From f863866183f58148508dbdb89c5818bd914dbf3c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 31 Mar 2022 13:20:28 -0500 Subject: [PATCH 001/394] Update to Spring v5.2.20. Requires also explicitly specifying version of spring-context-support to avoid dependency convergence issues. --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1f12c0c9f281..d66e81c9328a 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 11 - 5.2.5.RELEASE + 5.2.20.RELEASE 2.2.6.RELEASE 5.2.2.RELEASE 5.4.10.Final @@ -1218,6 +1218,12 @@ ${spring.version} + + org.springframework + spring-context-support + ${spring.version} + + org.springframework spring-test From fe9d22a4f6cc43de97fbcec060a09e484b90012b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 1 Apr 2022 09:07:33 -0500 Subject: [PATCH 002/394] Change to snapshot version --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 30 +++++++++++++++--------------- 15 files changed, 30 insertions(+), 30 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index f000f2e1babf..8d6b7333087f 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index ce1dc8a3263f..244cbae25601 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index f4acb0d29752..c29ae237705d 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 8a25dece7e8e..635fff3de7ab 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index c60383384fe2..15550950a159 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.2 + 7.2.1-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 20395013ceee..1c56bdc1b6b6 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 7f08f304d772..74868fd757d6 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index a55e0457a970..80f5bac9c32b 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index a9a58cff06d1..2c5da3ff452a 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 88ddce8b3219..d0471ebfa7cf 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 7deb4bd0d097..f5c3813f303e 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 6f4097f3d715..f604809754f8 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index fadd08b01455..6f9924675024 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.2 + 7.2.1-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 54819ee041e8..23afe092c0e9 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.2 + 7.2.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index d66e81c9328a..681606eaf0fd 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.2 + 7.2.1-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -868,14 +868,14 @@ org.dspace dspace-rest - 7.2 + 7.2.1-SNAPSHOT jar classes org.dspace dspace-rest - 7.2 + 7.2.1-SNAPSHOT war @@ -1026,69 +1026,69 @@ org.dspace dspace-api - 7.2 + 7.2.1-SNAPSHOT org.dspace dspace-api test-jar - 7.2 + 7.2.1-SNAPSHOT test org.dspace.modules additions - 7.2 + 7.2.1-SNAPSHOT org.dspace dspace-sword - 7.2 + 7.2.1-SNAPSHOT org.dspace dspace-swordv2 - 7.2 + 7.2.1-SNAPSHOT org.dspace dspace-oai - 7.2 + 7.2.1-SNAPSHOT org.dspace dspace-services - 7.2 + 7.2.1-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.2 + 7.2.1-SNAPSHOT test org.dspace dspace-rdf - 7.2 + 7.2.1-SNAPSHOT org.dspace dspace-iiif - 7.2 + 7.2.1-SNAPSHOT org.dspace dspace-server-webapp - 7.2 + 7.2.1-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.2 + 7.2.1-SNAPSHOT war From e8b92088f128bcb7b091cf59e048693ec00fb134 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 1 Apr 2022 09:21:54 -0500 Subject: [PATCH 003/394] [maven-release-plugin] prepare release dspace-7.2.1 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 8d6b7333087f..f0f73d9ca306 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 244cbae25601..303be411e4ca 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index c29ae237705d..1a6b87b051c1 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 635fff3de7ab..b68f727ea781 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 15550950a159..e4f46a0b9581 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.2.1-SNAPSHOT + 7.2.1 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 1c56bdc1b6b6..8ac636239c7e 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 74868fd757d6..75d2f3855c61 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 80f5bac9c32b..25eb7c8ccb15 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 2c5da3ff452a..5a507edd60d4 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index d0471ebfa7cf..5b494781affb 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index f5c3813f303e..57daef684715 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index f604809754f8..4d97118d362d 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 6f9924675024..b01cef062e78 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.2.1-SNAPSHOT + 7.2.1 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 23afe092c0e9..a1bdbfca2e28 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.2.1-SNAPSHOT + 7.2.1 ../pom.xml diff --git a/pom.xml b/pom.xml index 681606eaf0fd..e92dbdcc8bf2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.2.1-SNAPSHOT + 7.2.1 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -868,14 +868,14 @@ org.dspace dspace-rest - 7.2.1-SNAPSHOT + 7.2.1 jar classes org.dspace dspace-rest - 7.2.1-SNAPSHOT + 7.2.1 war @@ -1026,69 +1026,69 @@ org.dspace dspace-api - 7.2.1-SNAPSHOT + 7.2.1 org.dspace dspace-api test-jar - 7.2.1-SNAPSHOT + 7.2.1 test org.dspace.modules additions - 7.2.1-SNAPSHOT + 7.2.1 org.dspace dspace-sword - 7.2.1-SNAPSHOT + 7.2.1 org.dspace dspace-swordv2 - 7.2.1-SNAPSHOT + 7.2.1 org.dspace dspace-oai - 7.2.1-SNAPSHOT + 7.2.1 org.dspace dspace-services - 7.2.1-SNAPSHOT + 7.2.1 org.dspace dspace-server-webapp test-jar - 7.2.1-SNAPSHOT + 7.2.1 test org.dspace dspace-rdf - 7.2.1-SNAPSHOT + 7.2.1 org.dspace dspace-iiif - 7.2.1-SNAPSHOT + 7.2.1 org.dspace dspace-server-webapp - 7.2.1-SNAPSHOT + 7.2.1 jar classes org.dspace dspace-server-webapp - 7.2.1-SNAPSHOT + 7.2.1 war @@ -1883,7 +1883,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.2 + dspace-7.2.1 From 8ee4d9f9ab1223c7be5d913d99e8ef33a487c70b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 1 Apr 2022 09:21:58 -0500 Subject: [PATCH 004/394] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index f0f73d9ca306..c649d06002e0 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 303be411e4ca..19d4fbe1ab14 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 1a6b87b051c1..11faf5f9211f 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index b68f727ea781..52cc15742ef9 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index e4f46a0b9581..21f5692026ca 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.2.1 + 7.2.2-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 8ac636239c7e..a76d8daa966a 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 75d2f3855c61..c46e79c6cd64 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 25eb7c8ccb15..5491d85ee18c 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 5a507edd60d4..cc8dd3b24bbc 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 5b494781affb..68b9e940c63b 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 57daef684715..0580e234abfd 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 4d97118d362d..d7c92507308b 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index b01cef062e78..58f832ab3532 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.2.1 + 7.2.2-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index a1bdbfca2e28..4e5c6d4b61de 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.2.1 + 7.2.2-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index e92dbdcc8bf2..8dc63ca4234b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.2.1 + 7.2.2-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -868,14 +868,14 @@ org.dspace dspace-rest - 7.2.1 + 7.2.2-SNAPSHOT jar classes org.dspace dspace-rest - 7.2.1 + 7.2.2-SNAPSHOT war @@ -1026,69 +1026,69 @@ org.dspace dspace-api - 7.2.1 + 7.2.2-SNAPSHOT org.dspace dspace-api test-jar - 7.2.1 + 7.2.2-SNAPSHOT test org.dspace.modules additions - 7.2.1 + 7.2.2-SNAPSHOT org.dspace dspace-sword - 7.2.1 + 7.2.2-SNAPSHOT org.dspace dspace-swordv2 - 7.2.1 + 7.2.2-SNAPSHOT org.dspace dspace-oai - 7.2.1 + 7.2.2-SNAPSHOT org.dspace dspace-services - 7.2.1 + 7.2.2-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.2.1 + 7.2.2-SNAPSHOT test org.dspace dspace-rdf - 7.2.1 + 7.2.2-SNAPSHOT org.dspace dspace-iiif - 7.2.1 + 7.2.2-SNAPSHOT org.dspace dspace-server-webapp - 7.2.1 + 7.2.2-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.2.1 + 7.2.2-SNAPSHOT war @@ -1883,7 +1883,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.2.1 + dspace-7.2 From e08fa2ec3148eff38ed8af815a8027fa91034ab9 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 May 2022 10:40:18 +1200 Subject: [PATCH 005/394] Dockerfile.test and docker-compose: expose port 8000, set JDWP java opts This allows a remote JVM debugger to be connected to port 8000 on the dspace docker container (test environment). This may need a documentation note to warn against running the supplied docker-compose / Dockerfile.test config in production -- already a good warning but made more important now that the JVM can be exposed to a debugger for reading memory... --- Dockerfile.test | 4 ++-- docker-compose.yml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile.test b/Dockerfile.test index 568ff9b60aa4..31e1f2e05dea 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -58,9 +58,9 @@ COPY --from=ant_build /dspace $DSPACE_INSTALL # NOTE: secretRequired="false" should only be used when AJP is NOT accessible from an external network. But, secretRequired="true" isn't supported by mod_proxy_ajp until Apache 2.5 RUN sed -i '/Service name="Catalina".*/a \\n ' $TOMCAT_INSTALL/conf/server.xml # Expose Tomcat port and AJP port -EXPOSE 8080 8009 +EXPOSE 8080 8009 8000 # Give java extra memory (2GB) -ENV JAVA_OPTS=-Xmx2000m +ENV JAVA_OPTS=-Xmx2000m\ -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:8000 # Link the DSpace 'server' webapp into Tomcat's webapps directory. # This ensures that when we start Tomcat, it runs from /server path (e.g. http://localhost:8080/server/) diff --git a/docker-compose.yml b/docker-compose.yml index f790257bdb81..6008b873ae5f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,8 @@ services: target: 8080 - published: 8009 target: 8009 + - published: 8000 + target: 8000 stdin_open: true tty: true volumes: From 8fb948d6a83c5cb1ccbf492a4ac76cb29b7bc49c Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 27 May 2022 12:24:49 -0400 Subject: [PATCH 006/394] Devolve authorization to the strategy classes. Add tests. #8304 --- .../RequestItemAuthorExtractor.java | 16 ++- .../RequestItemHelpdeskStrategy.java | 44 ++++-- .../RequestItemSubmitterStrategy.java | 21 ++- .../RequestItemHelpdeskStrategyTest.java | 128 ++++++++++++++++++ .../RequestItemSubmitterStrategyTest.java | 115 ++++++++++++++++ .../repository/RequestItemRepository.java | 16 +-- 6 files changed, 309 insertions(+), 31 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java create mode 100644 dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java index 9b66030e9030..e353637c7928 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java @@ -14,14 +14,14 @@ /** * Interface to abstract the strategy for select the author to contact for - * request copy + * request copy. * * @author Andrea Bollini */ public interface RequestItemAuthorExtractor { /** - * Retrieve the auhtor to contact for a request copy of the give item. + * Retrieve the author to contact for requesting a copy of the given item. * * @param context DSpace context object * @param item item to request @@ -29,5 +29,15 @@ public interface RequestItemAuthorExtractor { * or null if no valid email address was found. * @throws SQLException if database error */ - public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException; + public RequestItemAuthor getRequestItemAuthor(Context context, Item item) + throws SQLException; + + /** + * + * @param context current DSpace session. + * @param item the requested Item. + * @return true if {@link eperson} is authorized to respond to requests for + * {@link item}. + */ + public boolean isAuthorized(Context context, Item item); } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java index 7b63d3ea8dae..4a38dc64882f 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java @@ -16,33 +16,40 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; /** - * RequestItem strategy to allow DSpace support team's helpdesk to receive requestItem request - * With this enabled, then the Item author/submitter doesn't receive the request, but the helpdesk instead does. + * RequestItem strategy to allow DSpace support team's help desk to receive + * requestItem requests. With this enabled, the Item author/submitter doesn't + * receive the request, but the help desk instead does. * - * Failover to the RequestItemSubmitterStrategy, which means the submitter would get the request if there is no - * specified helpdesk email. + *

Fails over to the {@link RequestItemSubmitterStrategy}, which means the + * submitter would get the request if there is no specified help desk email. * * @author Sam Ottenhoff * @author Peter Dietz */ -public class RequestItemHelpdeskStrategy extends RequestItemSubmitterStrategy { +public class RequestItemHelpdeskStrategy + extends RequestItemSubmitterStrategy { + static final String P_HELPDESK_OVERRIDE + = "request.item.helpdesk.override"; + static final String P_MAIL_HELPDESK = "mail.helpdesk"; + @Autowired(required = true) protected EPersonService ePersonService; + @Autowired(required = true) + protected ConfigurationService configurationService; + public RequestItemHelpdeskStrategy() { } @Override - public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException { - ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); + public RequestItemAuthor getRequestItemAuthor(Context context, Item item) + throws SQLException { boolean helpdeskOverridesSubmitter = configurationService - .getBooleanProperty("request.item.helpdesk.override", false); - String helpDeskEmail = configurationService.getProperty("mail.helpdesk"); + .getBooleanProperty(P_HELPDESK_OVERRIDE, false); + String helpDeskEmail = configurationService.getProperty(P_MAIL_HELPDESK); if (helpdeskOverridesSubmitter && StringUtils.isNotBlank(helpDeskEmail)) { return getHelpDeskPerson(context, helpDeskEmail); @@ -53,16 +60,18 @@ public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws } /** - * Return a RequestItemAuthor object for the specified helpdesk email address. - * It makes an attempt to find if there is a matching eperson for the helpdesk address, to use the name, - * Otherwise it falls back to a helpdeskname key in the Messages.props. + * Return a RequestItemAuthor object for the specified help desk email address. + * It makes an attempt to find if there is a matching {@link EPerson} for + * the help desk address, to use its name. Otherwise it falls back to the + * {@code helpdeskname} key in {@code Messages.properties}. * * @param context context * @param helpDeskEmail email * @return RequestItemAuthor * @throws SQLException if database error */ - public RequestItemAuthor getHelpDeskPerson(Context context, String helpDeskEmail) throws SQLException { + public RequestItemAuthor getHelpDeskPerson(Context context, String helpDeskEmail) + throws SQLException { context.turnOffAuthorisationSystem(); EPerson helpdeskEPerson = ePersonService.findByEmail(context, helpDeskEmail); context.restoreAuthSystemState(); @@ -76,4 +85,9 @@ public RequestItemAuthor getHelpDeskPerson(Context context, String helpDeskEmail return new RequestItemAuthor(helpdeskName, helpDeskEmail); } } + + @Override + public boolean isAuthorized(Context context, Item item) { + return true; + } } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java index 2708c24ba9fa..a08d2f0723f4 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java @@ -9,6 +9,8 @@ import java.sql.SQLException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -19,6 +21,7 @@ * @author Andrea Bollini */ public class RequestItemSubmitterStrategy implements RequestItemAuthorExtractor { + private static final Logger LOG = LogManager.getLogger(); public RequestItemSubmitterStrategy() { } @@ -32,7 +35,7 @@ public RequestItemSubmitterStrategy() { */ @Override public RequestItemAuthor getRequestItemAuthor(Context context, Item item) - throws SQLException { + throws SQLException { EPerson submitter = item.getSubmitter(); RequestItemAuthor author = null; if (null != submitter) { @@ -41,4 +44,20 @@ public RequestItemAuthor getRequestItemAuthor(Context context, Item item) } return author; } + + @Override + public boolean isAuthorized(Context context, Item item) { + RequestItemAuthor authorizer; + try { + authorizer = getRequestItemAuthor(context, item); + } catch (SQLException ex) { + LOG.warn("Failed to find an authorizer: {}", ex::getMessage); + return false; + } + EPerson user = context.getCurrentUser(); + if (null == user) { + return false; + } + return authorizer.getEmail().equals(user.getEmail()); + } } diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java new file mode 100644 index 000000000000..eaafcb58c27c --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java @@ -0,0 +1,128 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.requestitem; + +import java.sql.SQLException; +import static org.junit.Assert.assertTrue; + +import org.dspace.AbstractUnitTest; +import org.dspace.builder.AbstractBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.AfterClass; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +/** + * + * @author mwood + */ +public class RequestItemHelpdeskStrategyTest + extends AbstractUnitTest { + private static final String HELPDESK_ADDRESS = "helpdesk@example.com"; + private static final String AUTHOR_ADDRESS = "john.doe@example.com"; + + private static ConfigurationService configurationService; + private static EPersonService epersonService; + private static EPerson johnDoe; + + private Item item; + + @BeforeClass + public static void setUpClass() + throws SQLException { + AbstractBuilder.init(); // AbstractUnitTest doesn't do this for us. + + configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + epersonService = EPersonServiceFactory.getInstance().getEPersonService(); + + Context ctx = new Context(); + ctx.turnOffAuthorisationSystem(); + johnDoe = EPersonBuilder.createEPerson(ctx) + .withEmail(AUTHOR_ADDRESS) + .withNameInMetadata("John", "Doe") + .build(); + ctx.restoreAuthSystemState(); + ctx.complete(); + } + + @AfterClass + public static void tearDownClass() { + AbstractBuilder.destroy(); // AbstractUnitTest doesn't do this for us. + } + + @Before + public void setUp() { + context = new Context(); + context.setCurrentUser(johnDoe); + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + item = ItemBuilder.createItem(context, collection) + .build(); + context.restoreAuthSystemState(); + context.setCurrentUser(null); + } + + /** + * Test of getRequestItemAuthor method, of class RequestItemHelpdeskStrategy. + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetRequestItemAuthor() + throws Exception { + RequestItemHelpdeskStrategy instance = new RequestItemHelpdeskStrategy(); + instance.configurationService = configurationService; + instance.ePersonService = epersonService; + + // Check with help desk enabled. + configurationService.setProperty(RequestItemHelpdeskStrategy.P_HELPDESK_OVERRIDE, "true"); + configurationService.setProperty(RequestItemHelpdeskStrategy.P_MAIL_HELPDESK, HELPDESK_ADDRESS); + RequestItemAuthor author = instance.getRequestItemAuthor(context, item); + assertEquals("Wrong author address", HELPDESK_ADDRESS, author.getEmail()); + + // Check with help desk disabled. + configurationService.setProperty(RequestItemHelpdeskStrategy.P_HELPDESK_OVERRIDE, "false"); + author = instance.getRequestItemAuthor(context, item); + assertEquals("Wrong author address", AUTHOR_ADDRESS, author.getEmail()); + } + + /** + * Test of getHelpDeskPerson method, of class RequestItemHelpdeskStrategy. + * @throws java.lang.Exception passed through. + */ + @Ignore + @Test + public void testGetHelpDeskPerson() throws Exception { + } + + /** + * Silly test of isAuthorized method. + */ + @Test + public void testIsAuthorized() { + RequestItemHelpdeskStrategy instance = new RequestItemHelpdeskStrategy(); + instance.configurationService = configurationService; + instance.ePersonService = epersonService; + assertTrue(instance.isAuthorized(context, item)); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java new file mode 100644 index 000000000000..1b1f91ac9482 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java @@ -0,0 +1,115 @@ +/** + * 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.requestitem; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.sql.SQLException; + +import org.dspace.AbstractUnitTest; +import org.dspace.builder.AbstractBuilder; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @author mwood + */ +public class RequestItemSubmitterStrategyTest + extends AbstractUnitTest { + private static final String AUTHOR_ADDRESS = "john.doe@example.com"; + private static final String NON_AUTHOR_ADDRESS = "richard.roe@example.com"; + + private static EPerson johnDoe; + private static EPerson richardRoe; + + private Item item; + + @BeforeClass + public static void setUpClass() + throws SQLException { + AbstractBuilder.init(); // AbstractUnitTest doesn't do this for us. + + Context ctx = new Context(); + ctx.turnOffAuthorisationSystem(); + johnDoe = EPersonBuilder.createEPerson(ctx) + .withEmail(AUTHOR_ADDRESS) + .withNameInMetadata("John", "Doe") + .build(); + richardRoe = EPersonBuilder.createEPerson(ctx) + .withEmail(NON_AUTHOR_ADDRESS) + .withNameInMetadata("Richard", "Roe") + .build(); + ctx.restoreAuthSystemState(); + ctx.complete(); + } + + @AfterClass + public static void tearDownClass() { + AbstractBuilder.destroy(); // AbstractUnitTest doesn't do this for us. + } + + @Before + public void setUp() { + context = new Context(); + context = new Context(); + context.setCurrentUser(johnDoe); + context.turnOffAuthorisationSystem(); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + item = ItemBuilder.createItem(context, collection) + .build(); + context.restoreAuthSystemState(); + context.setCurrentUser(null); + } + + /** + * Test of getRequestItemAuthor method, of class RequestItemSubmitterStrategy. + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetRequestItemAuthor() + throws Exception { + RequestItemSubmitterStrategy instance = new RequestItemSubmitterStrategy(); + RequestItemAuthor author = instance.getRequestItemAuthor(context, item); + assertEquals("Wrong author address", AUTHOR_ADDRESS, author.getEmail()); + } + + /** + * Test of isAuthorized method, of class RequestItemSubmitterStrategy. + */ + @Test + public void testIsAuthorized() { + RequestItemSubmitterStrategy instance = new RequestItemSubmitterStrategy(); + + // Test with correct logged-in user. + context.setCurrentUser(johnDoe); + assertTrue("Should be authorized", instance.isAuthorized(context, item)); + + // Test with incorrect logged-in user. + context.setCurrentUser(richardRoe); + assertFalse("Should not be authorized", instance.isAuthorized(context, item)); + + // Test when not logged in. + context.setCurrentUser(null); + assertFalse("Should not be authorized", instance.isAuthorized(context, item)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index d013566a2c07..33faca1a30bf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -27,7 +27,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.requestitem.RequestItem; -import org.dspace.app.requestitem.RequestItemAuthor; import org.dspace.app.requestitem.RequestItemAuthorExtractor; import org.dspace.app.requestitem.RequestItemEmailNotifier; import org.dspace.app.requestitem.service.RequestItemService; @@ -197,7 +196,7 @@ public RequestItemRest createAndReturn(Context ctx) responseLink = getLinkTokenEmail(ri.getToken()); } catch (URISyntaxException | MalformedURLException e) { LOG.warn("Impossible URL error while composing email: {}", - e.getMessage()); + e::getMessage); throw new RuntimeException("Request not sent: " + e.getMessage()); } @@ -229,14 +228,7 @@ public RequestItemRest put(Context context, HttpServletRequest request, } // Check for authorized user - RequestItemAuthor authorizer; - try { - authorizer = requestItemAuthorExtractor.getRequestItemAuthor(context, ri.getItem()); - } catch (SQLException ex) { - LOG.warn("Failed to find an authorizer: {}", ex.getMessage()); - authorizer = new RequestItemAuthor("", ""); - } - if (!authorizer.getEmail().equals(context.getCurrentUser().getEmail())) { + if (!requestItemAuthorExtractor.isAuthorized(context, ri.getItem())) { throw new AuthorizeException("Not authorized to approve this request"); } @@ -267,7 +259,7 @@ public RequestItemRest put(Context context, HttpServletRequest request, try { RequestItemEmailNotifier.sendResponse(context, ri, subject, message); } catch (IOException ex) { - LOG.warn("Response not sent: {}", ex.getMessage()); + LOG.warn("Response not sent: {}", ex::getMessage); throw new RuntimeException("Response not sent", ex); } @@ -276,7 +268,7 @@ public RequestItemRest put(Context context, HttpServletRequest request, try { RequestItemEmailNotifier.requestOpenAccess(context, ri); } catch (IOException ex) { - LOG.warn("Open access request not sent: {}", ex.getMessage()); + LOG.warn("Open access request not sent: {}", ex::getMessage); throw new RuntimeException("Open access request not sent", ex); } } From a95647268447171fc751c1d5ce764176ea095f91 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 27 May 2022 12:59:44 -0400 Subject: [PATCH 007/394] Satisfy checkstyle. #8304 --- .../app/requestitem/RequestItemHelpdeskStrategyTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java index eaafcb58c27c..dfc8b80bb7ba 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java @@ -7,9 +7,11 @@ */ package org.dspace.app.requestitem; -import java.sql.SQLException; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.sql.SQLException; + import org.dspace.AbstractUnitTest; import org.dspace.builder.AbstractBuilder; import org.dspace.builder.CollectionBuilder; @@ -26,7 +28,6 @@ import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.AfterClass; -import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; From 13eaaf4ab8c8fa0122300c54f8f975020b4d37d8 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sat, 20 Aug 2022 10:18:17 +1200 Subject: [PATCH 008/394] Move JDWP configuration from JAVA_OPTS to CATALINA_OPTS --- Dockerfile.test | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile.test b/Dockerfile.test index 31e1f2e05dea..4be0944720b1 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -60,7 +60,9 @@ RUN sed -i '/Service name="Catalina".*/a \\n Date: Thu, 15 Sep 2022 14:18:01 -0400 Subject: [PATCH 009/394] Remove unused 'isAuthorized' methods. This method was always questionable. The point of Request a Copy is to bypass DSpace's authorizations. --- .../RequestItemAuthorExtractor.java | 11 +------- .../RequestItemHelpdeskStrategy.java | 5 ---- .../RequestItemSubmitterStrategy.java | 20 -------------- .../RequestItemHelpdeskStrategyTest.java | 21 ++++----------- .../RequestItemSubmitterStrategyTest.java | 27 +++---------------- 5 files changed, 9 insertions(+), 75 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java index b068cce83d39..5c6e48ee3f85 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java @@ -29,16 +29,7 @@ public interface RequestItemAuthorExtractor { * @return Names and email addresses to send the request to. * @throws SQLException if database error */ - @NonNull + @NonNull public List getRequestItemAuthor(Context context, Item item) throws SQLException; - - /** - * - * @param context current DSpace session. - * @param item the requested Item. - * @return true if {@link eperson} is authorized to respond to requests for - * {@link item}. - */ - public boolean isAuthorized(Context context, Item item); } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java index f2b25e98728c..dee0ed7a2351 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java @@ -91,9 +91,4 @@ public RequestItemAuthor getHelpDeskPerson(Context context, String helpDeskEmail return new RequestItemAuthor(helpdeskName, helpDeskEmail); } } - - @Override - public boolean isAuthorized(Context context, Item item) { - return true; - } } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java index 6e2b1455d525..cd2a16d7a772 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java @@ -49,24 +49,4 @@ public List getRequestItemAuthor(Context context, Item item) } return authors; } - - @Override - public boolean isAuthorized(Context context, Item item) { - List authorizers; - try { - authorizers = getRequestItemAuthor(context, item); - } catch (SQLException ex) { - LOG.warn("Failed to find an authorizer: {}", ex::getMessage); - return false; - } - EPerson user = context.getCurrentUser(); - if (null == user) { - return false; - } - boolean authorized = false; - for (RequestItemAuthor authorizer : authorizers) { - authorized |= authorizer.getEmail().equals(user.getEmail()); - } - return authorized; -} } diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java index dfc8b80bb7ba..b03d7576f991 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategyTest.java @@ -8,9 +8,9 @@ package org.dspace.app.requestitem; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import java.sql.SQLException; +import java.util.List; import org.dspace.AbstractUnitTest; import org.dspace.builder.AbstractBuilder; @@ -98,13 +98,13 @@ public void testGetRequestItemAuthor() // Check with help desk enabled. configurationService.setProperty(RequestItemHelpdeskStrategy.P_HELPDESK_OVERRIDE, "true"); configurationService.setProperty(RequestItemHelpdeskStrategy.P_MAIL_HELPDESK, HELPDESK_ADDRESS); - RequestItemAuthor author = instance.getRequestItemAuthor(context, item); - assertEquals("Wrong author address", HELPDESK_ADDRESS, author.getEmail()); + List authors = instance.getRequestItemAuthor(context, item); + assertEquals("Wrong author address", HELPDESK_ADDRESS, authors.get(0).getEmail()); // Check with help desk disabled. configurationService.setProperty(RequestItemHelpdeskStrategy.P_HELPDESK_OVERRIDE, "false"); - author = instance.getRequestItemAuthor(context, item); - assertEquals("Wrong author address", AUTHOR_ADDRESS, author.getEmail()); + authors = instance.getRequestItemAuthor(context, item); + assertEquals("Wrong author address", AUTHOR_ADDRESS, authors.get(0).getEmail()); } /** @@ -115,15 +115,4 @@ public void testGetRequestItemAuthor() @Test public void testGetHelpDeskPerson() throws Exception { } - - /** - * Silly test of isAuthorized method. - */ - @Test - public void testIsAuthorized() { - RequestItemHelpdeskStrategy instance = new RequestItemHelpdeskStrategy(); - instance.configurationService = configurationService; - instance.ePersonService = epersonService; - assertTrue(instance.isAuthorized(context, item)); - } } diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java index 1b1f91ac9482..10fc2c4d5278 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java @@ -8,10 +8,9 @@ package org.dspace.app.requestitem; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import java.sql.SQLException; +import java.util.List; import org.dspace.AbstractUnitTest; import org.dspace.builder.AbstractBuilder; @@ -89,27 +88,7 @@ public void setUp() { public void testGetRequestItemAuthor() throws Exception { RequestItemSubmitterStrategy instance = new RequestItemSubmitterStrategy(); - RequestItemAuthor author = instance.getRequestItemAuthor(context, item); - assertEquals("Wrong author address", AUTHOR_ADDRESS, author.getEmail()); - } - - /** - * Test of isAuthorized method, of class RequestItemSubmitterStrategy. - */ - @Test - public void testIsAuthorized() { - RequestItemSubmitterStrategy instance = new RequestItemSubmitterStrategy(); - - // Test with correct logged-in user. - context.setCurrentUser(johnDoe); - assertTrue("Should be authorized", instance.isAuthorized(context, item)); - - // Test with incorrect logged-in user. - context.setCurrentUser(richardRoe); - assertFalse("Should not be authorized", instance.isAuthorized(context, item)); - - // Test when not logged in. - context.setCurrentUser(null); - assertFalse("Should not be authorized", instance.isAuthorized(context, item)); + List author = instance.getRequestItemAuthor(context, item); + assertEquals("Wrong author address", AUTHOR_ADDRESS, author.get(0).getEmail()); } } From a4cb9bd129d7a905d2d5ef51dd0240e05682f861 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 16 Sep 2022 10:58:27 -0400 Subject: [PATCH 010/394] Remove more unused code. Caused by removing isAuthorized(). --- .../app/requestitem/RequestItemSubmitterStrategy.java | 4 ---- .../app/requestitem/RequestItemSubmitterStrategyTest.java | 7 ------- 2 files changed, 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java index cd2a16d7a772..6cfeee442600 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java @@ -11,8 +11,6 @@ import java.util.ArrayList; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -24,8 +22,6 @@ * @author Andrea Bollini */ public class RequestItemSubmitterStrategy implements RequestItemAuthorExtractor { - private static final Logger LOG = LogManager.getLogger(); - public RequestItemSubmitterStrategy() { } diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java index 10fc2c4d5278..f485a591b079 100644 --- a/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/RequestItemSubmitterStrategyTest.java @@ -35,10 +35,8 @@ public class RequestItemSubmitterStrategyTest extends AbstractUnitTest { private static final String AUTHOR_ADDRESS = "john.doe@example.com"; - private static final String NON_AUTHOR_ADDRESS = "richard.roe@example.com"; private static EPerson johnDoe; - private static EPerson richardRoe; private Item item; @@ -53,10 +51,6 @@ public static void setUpClass() .withEmail(AUTHOR_ADDRESS) .withNameInMetadata("John", "Doe") .build(); - richardRoe = EPersonBuilder.createEPerson(ctx) - .withEmail(NON_AUTHOR_ADDRESS) - .withNameInMetadata("Richard", "Roe") - .build(); ctx.restoreAuthSystemState(); ctx.complete(); } @@ -68,7 +62,6 @@ public static void tearDownClass() { @Before public void setUp() { - context = new Context(); context = new Context(); context.setCurrentUser(johnDoe); context.turnOffAuthorisationSystem(); From 692fefcbac6e0b9eab9c0bf6f70552b342b3f1d3 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 16 Sep 2022 11:08:15 -0400 Subject: [PATCH 011/394] Don't require authentication to approve/deny a copy request. Possession of the token is sufficient. --- .../org/dspace/app/rest/repository/RequestItemRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 3f005634e9f7..5646713736d0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -218,7 +218,7 @@ public void delete(Context context, String token) } @Override - @PreAuthorize("isAuthenticated()") + @PreAuthorize("permitAll()") public RequestItemRest put(Context context, HttpServletRequest request, String apiCategory, String model, String token, JsonNode requestBody) throws AuthorizeException { From fa5aa9c99347c1aa087b5ef7fc9b44bc8f0b91a2 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 16 Sep 2022 11:53:11 -0400 Subject: [PATCH 012/394] Don't test for authNZ failures that can no longer occur. --- .../app/rest/RequestItemRepositoryIT.java | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index d7eb707bbb5a..769f67e2c091 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -533,29 +533,6 @@ public void testPutBadRequest() ObjectWriter mapperWriter = new ObjectMapper().writer(); - // Unauthenticated user - parameters = Map.of( - "acceptRequest", "true", - "subject", "subject", - "responseMessage", "Request accepted"); - content = mapperWriter.writeValueAsString(parameters); - getClient().perform(put(URI_ROOT + '/' + itemRequest.getToken()) - .contentType(contentType) - .content(content)) - .andExpect(status().isUnauthorized()); - - // Unauthorized user - parameters = Map.of( - "acceptRequest", "true", - "subject", "subject", - "responseMessage", "Request accepted"); - content = mapperWriter.writeValueAsString(parameters); - authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(put(URI_ROOT + '/' + itemRequest.getToken()) - .contentType(contentType) - .content(content)) - .andExpect(status().isInternalServerError()); // Should be FORBIDDEN - // Missing acceptRequest parameters = Map.of( "subject", "subject", From 38946f8de55b493358af7ddd812810c2f300c189 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 16 Sep 2022 14:47:16 -0400 Subject: [PATCH 013/394] Test that unauthenticated session can (dis)approve a request. --- .../app/rest/RequestItemRepositoryIT.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java index 769f67e2c091..4a90efb2c1bc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RequestItemRepositoryIT.java @@ -517,6 +517,33 @@ public void testPut() within(1, ChronoUnit.MINUTES, new Date())); } + @Test + public void testPutUnauthenticated() + throws Exception { + System.out.println("put unauthenticated request"); + RequestItem itemRequest = RequestItemBuilder + .createRequestItem(context, item, bitstream) + .build(); + + Map parameters; + String content; + + ObjectWriter mapperWriter = new ObjectMapper().writer(); + + // Unauthenticated user should be allowed. + parameters = Map.of( + "acceptRequest", "true", + "subject", "put unauthenticated", + "responseMessage", "Request accepted", + "suggestOpenAccess", "false"); + + content = mapperWriter.writeValueAsString(parameters); + getClient().perform(put(URI_ROOT + '/' + itemRequest.getToken()) + .contentType(contentType) + .content(content)) + .andExpect(status().isOk()); + } + @Test public void testPutBadRequest() throws Exception { From b1423de7c1f72e9916c31f51dc6ec825f010657b Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 16 Sep 2022 14:47:54 -0400 Subject: [PATCH 014/394] Noticed in testing: okay not to have a current user. --- .../dspace/app/requestitem/RequestItemEmailNotifier.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index 02054ee1a0fc..b06dbbcd99a0 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -220,8 +220,13 @@ static public void requestOpenAccess(Context context, RequestItem ri) message.addArgument(bitstreamName); // {0} bitstream name or "all" message.addArgument(item.getHandle()); // {1} Item handle message.addArgument(ri.getToken()); // {2} Request token - message.addArgument(approver.getFullName()); // {3} Approver's name - message.addArgument(approver.getEmail()); // {4} Approver's address + if (approver != null) { + message.addArgument(approver.getFullName()); // {3} Approver's name + message.addArgument(approver.getEmail()); // {4} Approver's address + } else { + message.addArgument("anonymous approver"); // [3] Approver's name + message.addArgument(configurationService.getProperty("mail.admin")); // [4] Approver's address + } // Who gets this message? String recipient; From b4b67c4322b71ed804b1e49a6319025ee94d367b Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 20 Sep 2022 15:40:18 -0400 Subject: [PATCH 015/394] Clean up an unused logger. --- .../dspace/app/requestitem/RequestItemSubmitterStrategy.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java index cd2a16d7a772..6cfeee442600 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java @@ -11,8 +11,6 @@ import java.util.ArrayList; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -24,8 +22,6 @@ * @author Andrea Bollini */ public class RequestItemSubmitterStrategy implements RequestItemAuthorExtractor { - private static final Logger LOG = LogManager.getLogger(); - public RequestItemSubmitterStrategy() { } From 24c1d44e94ebcb58a19fb59b996dd4c043dd10b6 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 21 Sep 2022 13:00:41 -0400 Subject: [PATCH 016/394] Autowire by name so alias works. Uncomment all strategies, mark for autowiring and lazy loading. Also tidy up XML internal documentation. --- dspace-server-webapp/pom.xml | 4 ++ .../repository/RequestItemRepository.java | 3 +- dspace/config/spring/api/requestitem.xml | 52 +++++++++++-------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 559b77b43632..1d685387d215 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -569,6 +569,10 @@ 2.0.7 test + + javax.annotation + javax.annotation-api + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index 3f005634e9f7..36ebb42411ff 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -17,6 +17,7 @@ import java.sql.SQLException; import java.util.Date; import java.util.UUID; +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; @@ -72,7 +73,7 @@ public class RequestItemRepository @Autowired(required = true) protected RequestItemConverter requestItemConverter; - @Autowired(required = true) + @Resource(name = "requestItemAuthorExtractor") protected RequestItemAuthorExtractor requestItemAuthorExtractor; @Autowired(required = true) diff --git a/dspace/config/spring/api/requestitem.xml b/dspace/config/spring/api/requestitem.xml index 90c49156d541..a3bf458451c8 100644 --- a/dspace/config/spring/api/requestitem.xml +++ b/dspace/config/spring/api/requestitem.xml @@ -6,21 +6,22 @@ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" - default-autowire-candidates="*Service,*DAO,javax.sql.DataSource"> + default-autowire-candidates="*Service,*DAO,javax.sql.DataSource" + default-lazy-init="true"> Strategies for determining who receives Request Copy emails. - A copy request "strategy" class produces a list of addresses to which a - request email should be sent. Each strategy gets its addresses from a - different source. Select the one that meets your need, or use the - CombiningRequestItemStrategy to meld the lists from two or more other - strategies. + A copy request "strategy" class produces a list of addresses + to which a request email should be sent. Each strategy gets + its addresses from a different source. Select the one that + meets your need, or use the CombiningRequestItemStrategy to + meld the lists from two or more other strategies. - @@ -28,13 +29,12 @@ id="org.dspace.app.requestitem.RequestItemMetadataStrategy" autowire-candidate="true"> - + id="org.dspace.app.requestitem.RequestItemHelpdeskStrategy" + autowire-candidate="true"/> - - + id='org.dspace.app.requestitem.CollectionAdministratorsRequestItemStrategy' + autowire-candidate="true"> + + Send request emails to administrators of an Item's owning + Collection. + + - - - --> From e26f58399226bdd5ba5b78e1dbb186b90a4903e1 Mon Sep 17 00:00:00 2001 From: Nicolas Boulay Date: Tue, 13 Sep 2022 10:29:56 -0400 Subject: [PATCH 017/394] Send email even if copy request is refused --- .../requestitem/RequestItemEmailNotifier.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index 02054ee1a0fc..4c494b0a316d 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -154,9 +154,9 @@ static public void sendResponse(Context context, RequestItem ri, String subject, email.setContent("body", message); email.setSubject(subject); email.addRecipient(ri.getReqEmail()); - if (ri.isAccept_request()) { - // Attach bitstreams. - try { + // Attach bitstreams. + try { + if (ri.isAccept_request()) { if (ri.isAllfiles()) { Item item = ri.getItem(); List bundles = item.getBundles("ORIGINAL"); @@ -178,12 +178,12 @@ static public void sendResponse(Context context, RequestItem ri, String subject, bitstream.getName(), bitstream.getFormat(context).getMIMEType()); } - email.send(); - } catch (MessagingException | IOException | SQLException | AuthorizeException e) { - LOG.warn(LogHelper.getHeader(context, - "error_mailing_requestItem", e.getMessage())); - throw new IOException("Reply not sent: " + e.getMessage()); } + email.send(); + } catch (MessagingException | IOException | SQLException | AuthorizeException e) { + LOG.warn(LogHelper.getHeader(context, + "error_mailing_requestItem", e.getMessage())); + throw new IOException("Reply not sent: " + e.getMessage()); } LOG.info(LogHelper.getHeader(context, "sent_attach_requestItem", "token={}"), ri.getToken()); From d246fc4a14a8a00b686b7a0e19f8b24075d546a3 Mon Sep 17 00:00:00 2001 From: Nicolas Boulay Date: Thu, 29 Sep 2022 15:16:14 -0400 Subject: [PATCH 018/394] Make it possible to allow a silent refusal --- .../app/requestitem/RequestItemEmailNotifier.java | 10 +++++++++- dspace/config/dspace.cfg | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index 4c494b0a316d..435fa0f9cc88 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -178,8 +178,16 @@ static public void sendResponse(Context context, RequestItem ri, String subject, bitstream.getName(), bitstream.getFormat(context).getMIMEType()); } + email.send(); + } else { + boolean sendRejectEmail = configurationService + .getBooleanProperty("request.item.reject.email", true); + // Not all sites want the "refusal" to be sent back to the requester via + // email. However, by default, the rejection email is sent back. + if (sendRejectEmail) { + email.send(); + } } - email.send(); } catch (MessagingException | IOException | SQLException | AuthorizeException e) { LOG.warn(LogHelper.getHeader(context, "error_mailing_requestItem", e.getMessage())); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 5b747a8a3bb0..bfa1c5faeb0f 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1542,6 +1542,10 @@ log.report.dir = ${dspace.dir}/log request.item.type = all # Should all Request Copy emails go to the helpdesk instead of the item submitter? request.item.helpdesk.override = false +# Should a rejection of a copy request send an email back to the requester? +# Defaults to "true", which means a rejection email is sent back. +# Setting it to "false" results in a silent rejection. +request.item.reject.email = true #------------------------------------------------------------------# #------------------SUBMISSION CONFIGURATION------------------------# From 980fd0f8ad357d71017652113d535d8170db8905 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Fri, 30 Sep 2022 10:26:05 +0200 Subject: [PATCH 019/394] Fix last expected MDF in findAllPaginationTest --- .../org/dspace/app/rest/MetadatafieldRestRepositoryIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java index 56c8f637f10d..1826cd0fbb58 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java @@ -1052,7 +1052,8 @@ public void findAllPaginationTest() throws Exception { // Metadata fields are returned alphabetically. // So, on the last page we'll just ensure it *at least* includes the last field alphabetically .andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItems( - MetadataFieldMatcher.matchMetadataFieldByKeys("workflow", "score", null) + MetadataFieldMatcher.matchMetadataField( + alphabeticMdFields.get(alphabeticMdFields.size() - 1)) ))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/core/metadatafields?"), From b8908e533b7e2fac120cb9f93f9f34dd68d0b3f2 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 3 Oct 2022 15:58:49 -0400 Subject: [PATCH 020/394] Ask for (aliased) strategy by name. --- .../org/dspace/app/requestitem/RequestItemEmailNotifier.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index b06dbbcd99a0..ca5331e1a3b1 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -56,7 +56,8 @@ public class RequestItemEmailNotifier { private static final RequestItemAuthorExtractor requestItemAuthorExtractor = DSpaceServicesFactory.getInstance() .getServiceManager() - .getServiceByName(null, RequestItemAuthorExtractor.class); + .getServiceByName("requestItemAuthorExtractor", + RequestItemAuthorExtractor.class); private RequestItemEmailNotifier() {} From 7837fbacd1acfd99db1c968c406f81859a2d4571 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 6 Oct 2022 12:53:51 -0500 Subject: [PATCH 021/394] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 79094ddbb8e1..f83d4ad980ba 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index f3257ad76974..879fb1da6955 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index de872b867c30..59bcdf39a2df 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index dcba95e68222..6dbe3f346681 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 8040ce5a1d31..f4df15ab1c83 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.4 + 7.5-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 63145abfb9da..965fc938d343 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 778b5412c161..5eabcca707b3 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index f5ce69ac8579..3a0aa6df5cda 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 5f99fb78fb37..51d5dc2df363 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 74a90b027e2b..f525e39532af 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index e36035cd8ed1..a2ca42b9cba4 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 2ff004d94517..97ddb4a808b4 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 84117dcdfe43..c5ffb1742629 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.4 + 7.5-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index f0d7e3bbe804..cbf2e10c33e2 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.4 + 7.5-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index da2ada0e4d17..ac6433c947e7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.4 + 7.5-SNAPSHOT DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -872,14 +872,14 @@ org.dspace dspace-rest - 7.4 + 7.5-SNAPSHOT jar classes org.dspace dspace-rest - 7.4 + 7.5-SNAPSHOT war @@ -1030,69 +1030,69 @@ org.dspace dspace-api - 7.4 + 7.5-SNAPSHOT org.dspace dspace-api test-jar - 7.4 + 7.5-SNAPSHOT test org.dspace.modules additions - 7.4 + 7.5-SNAPSHOT org.dspace dspace-sword - 7.4 + 7.5-SNAPSHOT org.dspace dspace-swordv2 - 7.4 + 7.5-SNAPSHOT org.dspace dspace-oai - 7.4 + 7.5-SNAPSHOT org.dspace dspace-services - 7.4 + 7.5-SNAPSHOT org.dspace dspace-server-webapp test-jar - 7.4 + 7.5-SNAPSHOT test org.dspace dspace-rdf - 7.4 + 7.5-SNAPSHOT org.dspace dspace-iiif - 7.4 + 7.5-SNAPSHOT org.dspace dspace-server-webapp - 7.4 + 7.5-SNAPSHOT jar classes org.dspace dspace-server-webapp - 7.4 + 7.5-SNAPSHOT war @@ -1927,7 +1927,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - dspace-7.4 + HEAD From 75e1c0c27d84234bdd5743f5d549d24001131075 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 30 Sep 2022 15:38:35 +0300 Subject: [PATCH 022/394] pom.xml: update pdfbox to 2.0.27 See release notes for versions from the current 2.0.24 until 2.0.27 here: - 2.0.25: https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12310760&version=12350196 - 2.0.26: https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12310760&version=12350925 - 2.0.27: https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12310760&version=12351648 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index da2ada0e4d17..568350bf5865 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 9.4.48.v20220622 2.17.1 - 2.0.24 + 2.0.27 1.18.0 1.7.25 2.3.0 From e65c27777998b7b8b85a11bd0b08b41cd581e0e2 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 30 Sep 2022 15:57:52 +0300 Subject: [PATCH 023/394] pom.xml: update DBCP2 dependency Update commons-dbcp2 and commons-pool2 to latest stable versions. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index da2ada0e4d17..7fcd7ded0473 100644 --- a/pom.xml +++ b/pom.xml @@ -1479,7 +1479,7 @@ org.apache.commons commons-dbcp2 - 2.8.0 + 2.9.0 commons-fileupload @@ -1506,7 +1506,7 @@ org.apache.commons commons-pool2 - 2.9.0 + 2.11.1 commons-validator From d015c4b8e8a9834c52356b7926d8ab07dc603374 Mon Sep 17 00:00:00 2001 From: QZhao <42927895+qzhao-ulb@users.noreply.github.com> Date: Fri, 14 Oct 2022 10:41:43 +0200 Subject: [PATCH 024/394] Bugfix: item admin can't create new version item --- .../org/dspace/app/rest/repository/VersionRestRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java index 3f16217d7657..585fc1de9f0b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionRestRepository.java @@ -111,7 +111,7 @@ protected VersionRest createAndReturn(Context context, List stringList) } EPerson submitter = item.getSubmitter(); - boolean isAdmin = authorizeService.isAdmin(context); + boolean isAdmin = authorizeService.isAdmin(context, item); boolean canCreateVersion = configurationService.getBooleanProperty("versioning.submitterCanCreateNewVersion"); if (!isAdmin && !(canCreateVersion && Objects.equals(submitter, context.getCurrentUser()))) { From 528f1a4ff23d025d2d068953ffacf6a7de48a2c6 Mon Sep 17 00:00:00 2001 From: Hrafn Malmquist Date: Sat, 15 Oct 2022 01:14:30 +0100 Subject: [PATCH 025/394] Use removeAllBundles, defend against ConcurrentModificationException --- .../src/main/java/org/dspace/harvest/OAIHarvester.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java index 5a6b26da93ba..ba1882587c0c 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java +++ b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java @@ -567,11 +567,7 @@ protected void processRecord(Element record, String OREPrefix, final long curren // Import the actual bitstreams if (harvestRow.getHarvestType() == 3) { log.info("Running ORE ingest on: " + item.getHandle()); - - List allBundles = item.getBundles(); - for (Bundle bundle : allBundles) { - itemService.removeBundle(ourContext, item, bundle); - } + itemService.removeAllBundles(ourContext, item); ORExwalk.ingest(ourContext, item, oreREM, true); } } else { From 1047dca2fd4ea8d90f8ae03641e0588f873c61a7 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 17 Oct 2022 15:49:41 +0200 Subject: [PATCH 026/394] 95912: queries should be case-insensitive --- .../app/rest/utils/DiscoverQueryBuilder.java | 1 + .../app/rest/DiscoveryRestControllerIT.java | 140 ++++++++++++++++++ .../app/rest/matcher/FacetValueMatcher.java | 7 + 3 files changed, 148 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DiscoverQueryBuilder.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DiscoverQueryBuilder.java index c666d9d01d17..aa59788a241a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DiscoverQueryBuilder.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/DiscoverQueryBuilder.java @@ -240,6 +240,7 @@ private void fillFacetIntoQueryArgs(Context context, IndexableObject scope, Stri // "show more" url int facetLimit = pageSize + 1; //This should take care of the sorting for us + prefix = StringUtils.isNotBlank(prefix) ? prefix.toLowerCase() : null; queryArgs.addFacetField(new DiscoverFacetField(facet.getIndexFieldName(), facet.getType(), facetLimit, facet.getSortOrderSidebar(), StringUtils.trimToNull(prefix))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index 021006cc02e8..23c104faa8bc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -362,6 +362,146 @@ public void discoverFacetsAuthorTestWithPrefix() throws Exception { ; } + @Test + public void discoverFacetsAuthorTestWithPrefix_Capital_And_Special_Chars() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection").build(); + + Item publicItem1 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, John") + .withAuthor("Jan, Doe") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("S’Idan, Mo") + .withAuthor("Tick&Tock") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 3") + .withIssueDate("2016-02-13") + .withAuthor("M Akai") + .withAuthor("stIjn, SmITH") + .build(); + + Item publicItem4 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 4") + .withIssueDate("2012-05-13") + .withSubject("St Augustine") + .build(); + + Item publicItem5 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 5") + .withIssueDate("2015-11-23") + .withSubject("Health & Medicine") + .build(); + + Item publicItem6 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 6") + .withIssueDate("2003-07-11") + .withSubject("1% economy") + .build(); + + Item publicItem7 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 7") + .withIssueDate("2008-12-31") + .withSubject("I.T.") + .build(); + + Item publicItem8 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 8") + .withIssueDate("2013-07-21") + .withSubject("?Unknown") + .build(); + + context.restoreAuthSystemState(); + + // The prefix query for author queries should be case-insensitive and correctly handle special characters + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "Smith")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("Smith, John")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "S")) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher + .entryFacetWithoutSelfLink("Smith, John"), + FacetValueMatcher + .entryFacetWithoutSelfLink("S’Idan, Mo"), + FacetValueMatcher + .entryFacetWithoutSelfLink("stIjn, SmITH")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "M A")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("M Akai")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "S’I")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("S’Idan, Mo")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "Jan, D")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("Jan, Doe")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "Tick&")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("Tick&Tock")))); + + // Should also be the case for subject queries + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "St A")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher + .entryFacetWithoutSelfLink("St Augustine")))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Health & M")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher + .entryFacetWithoutSelfLink("Health & Medicine")))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "1% e")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("1% economy")))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "I.")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("I.T.")))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "?U")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("?Unknown")))); + } + @Test public void discoverFacetsAuthorTestForHasMoreFalse() throws Exception { //Turn of the authorization system so that we can create the structure specified below diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java index a68356da5322..2c7cebdbe973 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java @@ -31,6 +31,13 @@ public static Matcher entryAuthor(String label) { ); } + public static Matcher entryFacetWithoutSelfLink(String label) { + return allOf( + hasJsonPath("$.label", is(label)), + hasJsonPath("$.type", is("discover")) + ); + } + public static Matcher entryAuthorWithAuthority(String label, String authority, int count) { return allOf( hasJsonPath("$.authorityKey", is(authority)), From 6f02dd502de54d8c177fda99004232288522de4c Mon Sep 17 00:00:00 2001 From: Christian Scheible Date: Tue, 18 Oct 2022 09:28:59 +0200 Subject: [PATCH 027/394] Fixes #8528, refreshing spider files causes null pointer. --- .../main/java/org/dspace/statistics/util/StatisticsClient.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java index b1b31c0fe146..e45ce163ed77 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java +++ b/dspace-api/src/main/java/org/dspace/statistics/util/StatisticsClient.java @@ -16,6 +16,7 @@ import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.logging.log4j.Logger; +import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Get; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.statistics.factory.StatisticsServiceFactory; @@ -136,6 +137,7 @@ private static void updateSpiderFiles() { URL url = new URL(value); Get get = new Get(); + get.setProject(new Project()); get.setDest(new File(spiders, url.getHost() + url.getPath().replace("/", "-"))); get.setSrc(url); get.setUseTimestamp(true); From 93e72eff54cd618a54e7edab50a483a102767c34 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 18 Oct 2022 11:16:59 +0200 Subject: [PATCH 028/394] 96097: CreateMissingIdentifiersIT affecting other tests fix --- .../dspace/ctask/general/CreateMissingIdentifiersIT.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java index 383f87bbd74c..2688342358f6 100644 --- a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -21,6 +21,7 @@ import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.After; import org.junit.Test; /** @@ -76,4 +77,11 @@ public void testPerform() assertEquals("Curation should fail", Curator.CURATE_ERROR, curator.getStatus(TASK_NAME)); } + + @Override + @After + public void destroy() throws Exception { + super.destroy(); + DSpaceServicesFactory.getInstance().getServiceManager().getApplicationContext().refresh(); + } } From c425da2ffbfb4e56c1910dca9e065881471bcc9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 05:59:04 +0000 Subject: [PATCH 029/394] Bump jackson-databind from 2.12.6.1 to 2.13.4.1 Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.12.6.1 to 2.13.4.1. - [Release notes](https://github.com/FasterXML/jackson/releases) - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5fab4f3e8780..63ab46945166 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ 2.10.0 2.12.6 - 2.12.6.1 + 2.13.4.1 1.3.2 2.3.1 2.3.1 From 64d264a660b3aed34defd2823cee2d64f3bf0838 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Wed, 19 Oct 2022 15:30:28 +0200 Subject: [PATCH 030/394] [DURACOM-98] enable solr slow query logging --- dspace/solr/authority/conf/solrconfig.xml | 1 + dspace/solr/oai/conf/solrconfig.xml | 1 + dspace/solr/search/conf/solrconfig.xml | 1 + dspace/solr/statistics/conf/solrconfig.xml | 1 + 4 files changed, 4 insertions(+) diff --git a/dspace/solr/authority/conf/solrconfig.xml b/dspace/solr/authority/conf/solrconfig.xml index 373cbb661c6a..21f917ebf8ca 100644 --- a/dspace/solr/authority/conf/solrconfig.xml +++ b/dspace/solr/authority/conf/solrconfig.xml @@ -79,6 +79,7 @@ 200 false 2 + 1000 diff --git a/dspace/solr/oai/conf/solrconfig.xml b/dspace/solr/oai/conf/solrconfig.xml index 02789fa3c2a4..ce8d9ebe2060 100644 --- a/dspace/solr/oai/conf/solrconfig.xml +++ b/dspace/solr/oai/conf/solrconfig.xml @@ -88,6 +88,7 @@ 200 false 2 + 1000 diff --git a/dspace/solr/search/conf/solrconfig.xml b/dspace/solr/search/conf/solrconfig.xml index e1174dc4a9e2..97b1d1ddbbf6 100644 --- a/dspace/solr/search/conf/solrconfig.xml +++ b/dspace/solr/search/conf/solrconfig.xml @@ -99,6 +99,7 @@ 200 false 2 + 1000 diff --git a/dspace/solr/statistics/conf/solrconfig.xml b/dspace/solr/statistics/conf/solrconfig.xml index 9a6c16146243..2b1cff45373d 100644 --- a/dspace/solr/statistics/conf/solrconfig.xml +++ b/dspace/solr/statistics/conf/solrconfig.xml @@ -88,6 +88,7 @@ 200 false 2 + 1000 From d6cc202e6b79e5ebf7c63f27d51051017bfd6d50 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 19 Oct 2022 10:27:01 -0500 Subject: [PATCH 031/394] Require commons-text 1.10.0 in dependencyManagement. Remove unnecessary references to commons-text elsewhere in POMs --- dspace-api/pom.xml | 12 ------------ dspace-server-webapp/pom.xml | 12 ------------ dspace/modules/additions/pom.xml | 7 ------- dspace/modules/server/pom.xml | 7 ------- pom.xml | 5 +++++ 5 files changed, 5 insertions(+), 38 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index f83d4ad980ba..685005160094 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -589,13 +589,6 @@ solr-core test ${solr.client.version} - - - - org.apache.commons - commons-text - - org.apache.lucene @@ -846,11 +839,6 @@ - - org.apache.commons - commons-text - 1.9 - io.netty netty-buffer diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 965fc938d343..1565c688b4f8 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -429,11 +429,6 @@ org.apache.commons commons-collections4 - - org.apache.commons - commons-text - 1.9 - commons-validator commons-validator @@ -527,13 +522,6 @@ solr-core ${solr.client.version} test - - - - org.apache.commons - commons-text - - org.apache.lucene diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index f525e39532af..8ecdd8344f13 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -266,13 +266,6 @@ solr-core ${solr.client.version} test - - - - org.apache.commons - commons-text - - org.apache.lucene diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index c5ffb1742629..8c738b35f081 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -336,13 +336,6 @@ just adding new jar in the classloader solr-core ${solr.client.version} test - - - - org.apache.commons - commons-text - - org.apache.lucene diff --git a/pom.xml b/pom.xml index 5fab4f3e8780..dbbe04adbd34 100644 --- a/pom.xml +++ b/pom.xml @@ -1508,6 +1508,11 @@ commons-pool2 2.11.1 + + org.apache.commons + commons-text + 1.10.0 + commons-validator commons-validator From 79d70be6fb00cfb2f48f2f794ec188f511d6f1fe Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 21 Oct 2022 16:22:26 -0500 Subject: [PATCH 032/394] Update action to add issues to new triage board --- .github/workflows/issue_opened.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index 3ccdd22a0ddd..3190f1d32dd2 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -10,20 +10,16 @@ jobs: runs-on: ubuntu-latest steps: # Add the new issue to a project board, if it needs triage - # See https://github.com/marketplace/actions/create-project-card-action - - name: Add issue to project board + # See https://github.com/actions/add-to-project + - name: Add issue to triage board # Only add to project board if issue is flagged as "needs triage" or has no labels # NOTE: By default we flag new issues as "needs triage" in our issue template if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '') - uses: technote-space/create-project-card-action@v1 + uses: actions/add-to-project@v0.3.0 # Note, the authentication token below is an ORG level Secret. # It must be created/recreated manually via a personal access token with "public_repo" and "admin:org" permissions # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token # This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific) with: - GITHUB_TOKEN: ${{ secrets.ORG_PROJECT_TOKEN }} - PROJECT: DSpace Backlog - COLUMN: Triage - CHECK_ORG_PROJECT: true - # Ignore errors. - continue-on-error: true + github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + project-url: https://github.com/orgs/DSpace/projects/24 From 21aebee31953b11cf7485c780d7535dd839f0b15 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 21 Oct 2022 16:37:30 -0500 Subject: [PATCH 033/394] Update GitHub token for action --- .github/workflows/issue_opened.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index 3190f1d32dd2..c7b80513bef3 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -21,5 +21,5 @@ jobs: # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token # This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific) with: - github-token: ${{ secrets.ORG_PROJECT_TOKEN }} + github-token: ${{ secrets.TRIAGE_PROJECT_TOKEN }} project-url: https://github.com/orgs/DSpace/projects/24 From b3ad1d5606dc9c0b561777cedf83fa8d3947abf6 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 21 Oct 2022 16:50:05 -0500 Subject: [PATCH 034/394] Fix comment --- .github/workflows/issue_opened.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index c7b80513bef3..631bb4836d44 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -17,7 +17,7 @@ jobs: if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '') uses: actions/add-to-project@v0.3.0 # Note, the authentication token below is an ORG level Secret. - # It must be created/recreated manually via a personal access token with "public_repo" and "admin:org" permissions + # It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token # This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific) with: From ad4a13e7ab6a3f465f87dbfc1e57c9ff210404ed Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 24 Oct 2022 11:17:17 -0500 Subject: [PATCH 035/394] Update jackson core alongside databind Also bumped databind to absolute latest --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 63ab46945166..07672f597d33 100644 --- a/pom.xml +++ b/pom.xml @@ -30,8 +30,8 @@ 3.4.0 2.10.0 - 2.12.6 - 2.13.4.1 + 2.13.4 + 2.13.4.2 1.3.2 2.3.1 2.3.1 From e49d4c9f549d19ef3aca0cc40000891bf7dbb167 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Mon, 24 Oct 2022 15:20:31 -0500 Subject: [PATCH 036/394] changed to check that headerValue is not null before setting subject to it --- dspace-api/src/main/java/org/dspace/core/Email.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/core/Email.java b/dspace-api/src/main/java/org/dspace/core/Email.java index 6db27c9e4f18..64da629bcc53 100644 --- a/dspace-api/src/main/java/org/dspace/core/Email.java +++ b/dspace-api/src/main/java/org/dspace/core/Email.java @@ -355,7 +355,7 @@ public void send() throws MessagingException, IOException { for (String headerName : config.getArrayProperty("mail.message.headers")) { String headerValue = (String) vctx.get(headerName); if ("subject".equalsIgnoreCase(headerName)) { - if (null != subject) { + if (null != headerValue) { subject = headerValue; } } else if ("charset".equalsIgnoreCase(headerName)) { From fd5dda2f0e5888d2e57c1822e7ed1b142e0ba65f Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Wed, 26 Oct 2022 09:33:19 +0200 Subject: [PATCH 037/394] Expose configuration key cc.license.jurisdiction and use user-selected jurisdiction if this key is set to none --- .../java/org/dspace/license/CreativeCommonsServiceImpl.java | 5 ++++- dspace/config/dspace.cfg | 1 + dspace/config/modules/rest.cfg | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java index 96f110c10196..29ae952a1968 100644 --- a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java @@ -605,7 +605,10 @@ public Map retrieveFullAnswerMap(String licenseId, String langua } } - updateJurisdiction(fullParamMap); + // Replace the jurisdiction unless default value is set to none + if (!"none".equals(jurisdiction)) { + updateJurisdiction(fullParamMap); + } return fullParamMap; } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 8e532310c11b..6ad377a86a1d 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1004,6 +1004,7 @@ cc.license.classfilter = recombo, mark # http://api.creativecommons.org/rest/1.5/support/jurisdictions # Commented out means the license is unported. # (e.g. nz = New Zealand, uk = England and Wales, jp = Japan) +# or set value none for user-selected jurisdiction cc.license.jurisdiction = us # Locale for CC dialogs diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index e5959cb7bdf7..a12d1a0978a3 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -45,6 +45,7 @@ rest.properties.exposed = submit.type-bind.field rest.properties.exposed = google.recaptcha.key.site rest.properties.exposed = google.recaptcha.version rest.properties.exposed = google.recaptcha.mode +rest.properties.exposed = cc.license.jurisdiction #---------------------------------------------------------------# # These configs are used by the deprecated REST (v4-6) module # From 954cf43cafae50742e9278ff8c8ce2b38924bf5d Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Wed, 26 Oct 2022 10:57:29 +0200 Subject: [PATCH 038/394] Changes to use the translations of CC API when no default locale is configured and correct language code used for CC metadata --- .../CCLicenseConnectorServiceImpl.java | 2 +- .../license/CreativeCommonsServiceImpl.java | 9 +++--- .../SubmissionCCLicenseRestRepository.java | 29 +++++++++++++++++-- dspace/config/dspace.cfg | 2 +- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java index 619227432d7d..cdecadba5242 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -106,7 +106,7 @@ public Map retrieveLicenses(String language) { for (String license : licenses) { - String licenseUri = ccLicenseUrl + "/license/" + license; + String licenseUri = ccLicenseUrl + "/license/" + license + "?locale=" + language; HttpGet licenseHttpGet = new HttpGet(licenseUri); try (CloseableHttpResponse response = client.execute(licenseHttpGet)) { CCLicense ccLicense = retrieveLicenseObject(license, response); diff --git a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java index 96f110c10196..95dccd6c3477 100644 --- a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java @@ -430,9 +430,10 @@ private void removeLicenseField(Context context, Item item, String field) throws } - private void addLicenseField(Context context, Item item, String field, String value) throws SQLException { + private void addLicenseField(Context context, Item item, String field, String language, String value) + throws SQLException { String[] params = splitField(field); - itemService.addMetadata(context, item, params[0], params[1], params[2], params[3], value); + itemService.addMetadata(context, item, params[0], params[1], params[2], language, value); } @@ -688,12 +689,12 @@ public void addLicense(Context context, Item item, String licenseUri, String lic String uriField = getCCField("uri"); String nameField = getCCField("name"); - addLicenseField(context, item, uriField, licenseUri); + addLicenseField(context, item, uriField, null, licenseUri); if (configurationService.getBooleanProperty("cc.submit.addbitstream")) { setLicenseRDF(context, item, fetchLicenseRDF(doc)); } if (configurationService.getBooleanProperty("cc.submit.setname")) { - addLicenseField(context, item, nameField, licenseName); + addLicenseField(context, item, nameField, "en", licenseName); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java index 0dab42f9bd04..356b3f22bfc4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java @@ -8,11 +8,14 @@ package org.dspace.app.rest.repository; import java.util.List; +import java.util.Locale; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.SubmissionCCLicenseRest; import org.dspace.core.Context; import org.dspace.license.CCLicense; import org.dspace.license.service.CreativeCommonsService; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -29,10 +32,23 @@ public class SubmissionCCLicenseRestRepository extends DSpaceRestRepository findAll(final Context context, final Pageable pageable) { - List allCCLicenses = creativeCommonsService.findAllCCLicenses(); + String defaultCCLocale = configurationService.getProperty("cc.license.locale"); + + Locale currentLocale = context.getCurrentLocale(); + List allCCLicenses; + // when no default CC locale is defined, current locale is used + if (currentLocale != null && StringUtils.isBlank(defaultCCLocale)) { + allCCLicenses = creativeCommonsService.findAllCCLicenses(currentLocale.toString()); + } else { + allCCLicenses = creativeCommonsService.findAllCCLicenses(); + } return converter.toRestPage(allCCLicenses, pageable, utils.obtainProjection()); } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 8e532310c11b..55fedae08833 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1008,7 +1008,7 @@ cc.license.jurisdiction = us # Locale for CC dialogs # A locale in the form language or language-country. -# If no default locale is defined the CC default locale will be used +# If no default locale is defined the current supported locale will be used cc.license.locale = en From e3133460b88151c60bd350136fa740a875fd0563 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Thu, 27 Oct 2022 13:19:50 -0500 Subject: [PATCH 039/394] set last harvested time for successful OAI harvest --- dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java index 5a6b26da93ba..e656b2dc4d56 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java +++ b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java @@ -455,6 +455,7 @@ public void runHarvest() throws SQLException, IOException, AuthorizeException { harvestRow.setHarvestStartTime(startTime); harvestRow.setHarvestMessage("Harvest from " + oaiSource + " successful"); harvestRow.setHarvestStatus(HarvestedCollection.STATUS_READY); + harvestRow.setLastHarvested(startTime); log.info( "Harvest from " + oaiSource + " successful. The process took " + timeTaken + " milliseconds. Harvested " + currentRecord + " items."); From 8aa8518ed864cd9998f3146f7f7c1b974c973ee2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 26 Oct 2022 15:08:29 -0500 Subject: [PATCH 040/394] Switch GitHub action for labeling merge conflicts --- .github/workflows/label_merge_conflicts.yml | 24 ++++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml index dcbab18f1b57..acfc48cd41dd 100644 --- a/.github/workflows/label_merge_conflicts.yml +++ b/.github/workflows/label_merge_conflicts.yml @@ -5,21 +5,29 @@ name: Check for merge conflicts # NOTE: This means merge conflicts are only checked for when a PR is merged to main. on: push: - branches: - - main + branches: [ main ] + # So that the `conflict_label_name` is removed if conflicts are resolved, + # we allow this to run for `pull_request_target` so that github secrets are available. + pull_request_target: + types: [ synchronize ] jobs: triage: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write steps: - # See: https://github.com/mschilde/auto-label-merge-conflicts/ + # See: https://github.com/prince-chrismc/label-merge-conflicts-action - name: Auto-label PRs with merge conflicts - uses: mschilde/auto-label-merge-conflicts@v2.0 + uses: prince-chrismc/label-merge-conflicts-action@v2 # Add "merge conflict" label if a merge conflict is detected. Remove it when resolved. # Note, the authentication token is created automatically # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token with: - CONFLICT_LABEL_NAME: 'merge conflict' - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Ignore errors - continue-on-error: true + conflict_label_name: 'merge conflict' + github_token: ${{ secrets.GITHUB_TOKEN }} + conflict_comment: | + Hi @${author}, + Conflicts have been detected against the base branch. + Please [resolve these conflicts](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/about-merge-conflicts) as soon as you can. Thanks! From bf0038182ff43bc6b9ebaf76e6b4c7a790455a12 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 28 Oct 2022 09:28:15 +0300 Subject: [PATCH 041/394] dspace-api: allow overriding ImageMagick density Allow users to override ImageMagick's default density of 72 DPI for creating PDF thumbnails. In this case a density of 144 will perform a "2x" supersample, which greatly increases the quality of the res- ulting images at the expense of slightly longer execution time and higher memory usage. See: https://imagemagick.org/script/command-line-options.php#density See: https://stackoverflow.com/questions/49828544/how-to-make-thumbnails-using-imagickresizeimage-appear-sharper-php Fixes: https://github.com/DSpace/DSpace/issues/8514 --- .../app/mediafilter/ImageMagickThumbnailFilter.java | 13 +++++++++++++ dspace/config/dspace.cfg | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index a79fd42d5937..862943a30c2b 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -119,6 +119,19 @@ public File getImageFile(File f, int page, boolean verbose) f2.deleteOnExit(); ConvertCmd cmd = new ConvertCmd(); IMOperation op = new IMOperation(); + + // Optionally override ImageMagick's default density of 72 DPI to use a + // "supersample" when creating the PDF thumbnail. Note that I prefer to + // use the getProperty() method here instead of getIntPropert() because + // the latter always returns an integer (0 in the case it's not set). I + // would prefer to keep ImageMagick's default to itself rather than for + // us to set one. Also note that the density option *must* come before + // we open the input file. + String density = configurationService.getProperty(PRE + ".density"); + if (density != null) { + op.density(Integer.valueOf(density)); + } + String s = "[" + page + "]"; op.addImage(f.getAbsolutePath() + s); if (configurationService.getBooleanProperty(PRE + ".flatten", true)) { diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 8e532310c11b..6bebfc689ca2 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -547,6 +547,12 @@ filter.org.dspace.app.mediafilter.PDFBoxThumbnail.inputFormats = Adobe PDF # org.dspace.app.mediafilter.ImageMagickThumbnailFilter.cmyk_profile = /usr/share/ghostscript/9.18/iccprofiles/default_cmyk.icc # org.dspace.app.mediafilter.ImageMagickThumbnailFilter.srgb_profile = /usr/share/ghostscript/9.18/iccprofiles/default_rgb.icc +# Optional: override ImageMagick's default density of 72 when creating PDF thum- +# bnails. Greatly increases quality of resulting thumbnails, at the expense of +# slightly longer execution times and higher memory usage. Any integer over 72 +# will help, but recommend 144 for a "2x" supersample. +# org.dspace.app.mediafilter.ImageMagickThumbnailFilter.density = 144 + #### Crosswalk and Packager Plugin Settings #### # Crosswalks are used to translate external metadata formats into DSpace's internal format (DIM) # Packagers are used to ingest/export 'packages' (both content files and metadata) From 7c2aa7f86d0e92fce14c2e98598fbaf1b44e0535 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 28 Oct 2022 09:43:19 -0500 Subject: [PATCH 042/394] Bug fix to label_merge_conflicts GitHub action --- .github/workflows/label_merge_conflicts.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml index acfc48cd41dd..be97e3c8b7aa 100644 --- a/.github/workflows/label_merge_conflicts.yml +++ b/.github/workflows/label_merge_conflicts.yml @@ -6,10 +6,10 @@ name: Check for merge conflicts on: push: branches: [ main ] - # So that the `conflict_label_name` is removed if conflicts are resolved, - # we allow this to run for `pull_request_target` so that github secrets are available. - pull_request_target: - types: [ synchronize ] + # So that the `conflict_label_name` is removed if conflicts are resolved, + # we allow this to run for `pull_request_target` so that github secrets are available. + pull_request_target: + types: [ synchronize ] jobs: triage: From 616170df7af18110643fc7caad86dc86ac2fb2fb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 28 Oct 2022 09:55:53 -0500 Subject: [PATCH 043/394] Ensure label_merge_conflicts never runs on forks --- .github/workflows/label_merge_conflicts.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml index be97e3c8b7aa..a401e8db0530 100644 --- a/.github/workflows/label_merge_conflicts.yml +++ b/.github/workflows/label_merge_conflicts.yml @@ -13,6 +13,8 @@ on: jobs: triage: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' + if: github.repository == 'dspace/dspace' runs-on: ubuntu-latest permissions: issues: write From 517daaeea24974fd3dca89701eb39f1b839e7156 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 31 Oct 2022 12:17:07 +1300 Subject: [PATCH 044/394] [TLC-387] When retrieving S3 bitstream, strip -R from internal ID if bitstream is registered --- .../storage/bitstore/S3BitStoreService.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) 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 70eabbcc8049..388e34dca509 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 @@ -62,6 +62,8 @@ public class S3BitStoreService extends BaseBitStoreService { protected static final String DEFAULT_BUCKET_PREFIX = "dspace-asset-"; + // Prefix indicating a registered bitstream + protected final String REGISTERED_FLAG = "-R"; /** * log4j log */ @@ -246,6 +248,10 @@ public String generateId() { @Override public InputStream get(Bitstream bitstream) throws IOException { String key = getFullKey(bitstream.getInternalId()); + // Strip -R from bitstream key if it's registered + if (isRegisteredBitstream(key)) { + key = key.substring(REGISTERED_FLAG.length()); + } try { S3Object object = s3Service.getObject(new GetObjectRequest(bucketName, key)); return (object != null) ? object.getObjectContent() : null; @@ -312,6 +318,10 @@ public void put(Bitstream bitstream, InputStream in) throws IOException { @Override public Map about(Bitstream bitstream, Map attrs) throws IOException { String key = getFullKey(bitstream.getInternalId()); + // If this is a registered bitstream, strip the -R prefix before retrieving + if (isRegisteredBitstream(key)) { + key = key.substring(REGISTERED_FLAG.length()); + } try { ObjectMetadata objectMetadata = s3Service.getObjectMetadata(bucketName, key); if (objectMetadata != null) { @@ -587,4 +597,14 @@ public static void main(String[] args) throws Exception { store.get(id); */ } + + /** + * Is this a registered bitstream? (not stored via this service originally) + * @param internalId + * @return + */ + public boolean isRegisteredBitstream(String internalId) { + return internalId.startsWith(REGISTERED_FLAG); + } + } From bc41e60b70c6987d7f0b436cdedf07245bdea6d8 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 31 Oct 2022 15:05:43 +0200 Subject: [PATCH 045/394] build: harden issue_opened.yml permissions Signed-off-by: Alex --- .github/workflows/issue_opened.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index 631bb4836d44..5d7c1c30f7d3 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -5,6 +5,7 @@ on: issues: types: [opened] +permissions: {} jobs: automation: runs-on: ubuntu-latest From 6695396d28875845c3e9646519bc15a3ffea1540 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 31 Oct 2022 16:45:18 +0200 Subject: [PATCH 046/394] build: harden build.yml permissions Signed-off-by: Alex --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3ecaeee2431..150a4de1a76b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,9 @@ name: Build # Run this Build for all pushes / PRs to current branch on: [push, pull_request] +permissions: + contents: read # to fetch code (actions/checkout) + jobs: tests: runs-on: ubuntu-latest From 1cf5a5497df24787d86f44098b73ecbb50522d4b Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 31 Oct 2022 16:45:42 +0200 Subject: [PATCH 047/394] build: harden docker.yml permissions Signed-off-by: Alex --- .github/workflows/docker.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7f58a49f9e9e..90372b8d901e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,6 +12,9 @@ on: - 'dspace-**' pull_request: +permissions: + contents: read # to fetch code (actions/checkout) + jobs: docker: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' From 07f2cc0e33d1a5f29c38c20c88057acf1a457b69 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 31 Oct 2022 16:55:32 +0200 Subject: [PATCH 048/394] build: harden label_merge_conflicts.yml permissions Signed-off-by: Alex --- .github/workflows/label_merge_conflicts.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml index a401e8db0530..d71d244c2b02 100644 --- a/.github/workflows/label_merge_conflicts.yml +++ b/.github/workflows/label_merge_conflicts.yml @@ -11,13 +11,14 @@ on: pull_request_target: types: [ synchronize ] +permissions: {} + jobs: triage: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' if: github.repository == 'dspace/dspace' runs-on: ubuntu-latest permissions: - issues: write pull-requests: write steps: # See: https://github.com/prince-chrismc/label-merge-conflicts-action From 035d42c1947e746f24929ff62bf5d954f36f70ed Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Tue, 1 Nov 2022 17:15:56 -0400 Subject: [PATCH 049/394] Option to skip bitstreams like 'export -x' --- .../java/org/dspace/app/itemimport/ItemImport.java | 4 ++++ .../ItemImportCLIScriptConfiguration.java | 3 +++ .../app/itemimport/ItemImportServiceImpl.java | 13 ++++++++++++- .../app/itemimport/service/ItemImportService.java | 7 +++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java index 034bdca8c177..6870b94eee1d 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java @@ -67,6 +67,7 @@ public class ItemImport extends DSpaceRunnable { protected String eperson = null; protected String[] collections = null; protected boolean isTest = false; + protected boolean isExcludeContent = false; protected boolean isResume = false; protected boolean useWorkflow = false; protected boolean useWorkflowSendEmail = false; @@ -119,6 +120,8 @@ public void setup() throws ParseException { handler.logInfo("**Test Run** - not actually importing items."); } + isExcludeContent = commandLine.hasOption('x'); + if (commandLine.hasOption('p')) { template = true; } @@ -204,6 +207,7 @@ public void internalRun() throws Exception { .getItemImportService(); try { itemImportService.setTest(isTest); + itemImportService.setExcludeContent(isExcludeContent); itemImportService.setResume(isResume); itemImportService.setUseWorkflow(useWorkflow); itemImportService.setUseWorkflowSendEmail(useWorkflowSendEmail); diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java index 6582996f368e..4e2c41f0c0aa 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java @@ -55,6 +55,9 @@ public Options getOptions() { options.addOption(Option.builder("v").longOpt("validate") .desc("test run - do not actually import items") .hasArg(false).required(false).build()); + options.addOption(Option.builder("x").longOpt("exclude_content") + .desc("do not load or expect content bitstreams") + .hasArg(false).required(false).build()); options.addOption(Option.builder("p").longOpt("template") .desc("apply template") .hasArg(false).required(false).build()); diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java index 047a589b6681..076cc8ebe20e 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java @@ -62,6 +62,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.itemimport.service.ItemImportService; import org.dspace.app.util.LocalSchemaFilenameFilter; @@ -135,7 +136,7 @@ * allow the registration of files (bitstreams) into DSpace. */ public class ItemImportServiceImpl implements ItemImportService, InitializingBean { - private final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemImportServiceImpl.class); + private final Logger log = LogManager.getLogger(); private DSpaceRunnableHandler handler; @@ -181,6 +182,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea protected String tempWorkDir; protected boolean isTest = false; + protected boolean isExcludeContent = false; protected boolean isResume = false; protected boolean useWorkflow = false; protected boolean useWorkflowSendEmail = false; @@ -1403,6 +1405,10 @@ protected List processContentsFile(Context c, Item i, String path, protected void processContentFileEntry(Context c, Item i, String path, String fileName, String bundleName, boolean primary) throws SQLException, IOException, AuthorizeException { + if (isExcludeContent) { + return; + } + String fullpath = path + File.separatorChar + fileName; // get an input stream @@ -2342,6 +2348,11 @@ public void setTest(boolean isTest) { this.isTest = isTest; } + @Override + public void setExcludeContent(boolean isExcludeContent) { + this.isExcludeContent = isExcludeContent; + } + @Override public void setResume(boolean isResume) { this.isResume = isResume; diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java b/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java index 76314897ec91..e99ece31b9bb 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java @@ -211,6 +211,13 @@ public void replaceItems(Context c, List mycollections, String sourc */ public void setTest(boolean isTest); + /** + * Set exclude-content flag. + * + * @param isExcludeContent true or false + */ + public void setExcludeContent(boolean isExcludeContent); + /** * Set resume flag * From 8c88b4973ce8b63ae3cccd00725050deec86ce59 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Thu, 3 Nov 2022 17:48:07 +0100 Subject: [PATCH 050/394] [8563][DURACOM-101] Refactored regex usage Introduced a new dspace-api Regex-Pattern utility class. --- .../java/org/dspace/app/util/DCInput.java | 20 +++++- .../dspace/app/util/RegexPatternUtils.java | 65 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index d2830bf89a6c..7a44bbdd2fdf 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -7,9 +7,12 @@ */ package org.dspace.app.util; +import static org.apache.commons.lang3.StringUtils.equalsAnyIgnoreCase; + import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.annotation.Nullable; @@ -238,6 +241,21 @@ public DCInput(Map fieldMap, Map> listMap) } + public String extractRegex(Map fieldMap) { + String regex = fieldMap.get("regex"); + Pattern generatedPattern = null; + if (regex != null) { + try { + generatedPattern = RegexPatternUtils.computePattern(regex); + } catch (PatternSyntaxException e) { + log.warn("The regex field of input {} with value {} is invalid!", this.label, regex); + } + } + return Optional.ofNullable(generatedPattern) + .map(pattern -> regex) + .orElse(null); + } + /** * Is this DCInput for display in the given scope? The scope should be * either "workflow" or "submit", as per the input forms definition. If the @@ -547,7 +565,7 @@ public boolean validate(String value) { if (StringUtils.isNotBlank(value)) { try { if (StringUtils.isNotBlank(regex)) { - Pattern pattern = Pattern.compile(regex); + Pattern pattern = RegexPatternUtils.computePattern(regex); if (!pattern.matcher(value).matches()) { return false; } diff --git a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java new file mode 100644 index 000000000000..63b5715d5a4a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java @@ -0,0 +1,65 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.util; + +import static java.util.regex.Pattern.CASE_INSENSITIVE; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.commons.lang3.StringUtils; + +/** + * Utility class useful for check regex and patterns. + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + * + */ +public class RegexPatternUtils { + + // checks input having the format /{pattern}/{flags} + // allowed flags are: g,i,m,s,u,y + public static final String REGEX_INPUT_VALIDATOR = "(/?)(.+)\\1([gimsuy]*)"; + // flags usable inside regex definition using format (?i|m|s|u|y) + public static final String REGEX_FLAGS = "(?%s)"; + public static final Pattern PATTERN_REGEX_INPUT_VALIDATOR = + Pattern.compile(REGEX_INPUT_VALIDATOR, CASE_INSENSITIVE); + + /** + * Computes a pattern starting from a regex definition with flags that + * uses the standard format: /{regex}/{flags}. + * If it's a valid regex a non-null {@code Pattern} will be retrieved, + * an exception will be thrown otherwise. + * + * @param regex with format /{regex}/{flags} + * @return {@code Pattern} regex pattern instance + * @throws PatternSyntaxException + */ + public static final Pattern computePattern(String regex) throws PatternSyntaxException { + Matcher inputMatcher = PATTERN_REGEX_INPUT_VALIDATOR.matcher(regex); + Pattern pattern = null; + if (inputMatcher.matches()) { + String regexPattern = inputMatcher.group(2); + String regexFlags = + Optional.ofNullable(inputMatcher.group(3)) + .filter(StringUtils::isNotBlank) + .map(flags -> String.format(REGEX_FLAGS, flags)) + .orElse("") + .replaceAll("g", ""); + pattern = Pattern.compile(regexFlags + regexPattern); + } else { + pattern = Pattern.compile(regex); + } + return pattern; + } + + private RegexPatternUtils() {} + +} From ff44d3f5912441a9e8791896150080ce53d95fed Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Thu, 3 Nov 2022 18:50:27 +0100 Subject: [PATCH 051/394] [8563] [DURACOM-101] Unit tests for RegexPatternUtils --- .../dspace/app/util/RegexPatternUtils.java | 2 +- .../app/util/RegexPatternUtilsTest.java | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java diff --git a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java index 63b5715d5a4a..a31aee805d2f 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java @@ -36,7 +36,7 @@ public class RegexPatternUtils { * Computes a pattern starting from a regex definition with flags that * uses the standard format: /{regex}/{flags}. * If it's a valid regex a non-null {@code Pattern} will be retrieved, - * an exception will be thrown otherwise. + * a {@link PatternSyntaxException} exception will be thrown otherwise. * * @param regex with format /{regex}/{flags} * @return {@code Pattern} regex pattern instance diff --git a/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java new file mode 100644 index 000000000000..147144eeecc7 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java @@ -0,0 +1,161 @@ +/** + * 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.util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.dspace.AbstractUnitTest; +import org.junit.Test; + +/** + * Tests for RegexPatternUtils + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + * + */ +public class RegexPatternUtilsTest extends AbstractUnitTest { + + @Test + public void testValidRegexWithFlag() { + final String insensitiveWord = "/[a-z]+/i"; + Pattern computePattern = Pattern.compile(insensitiveWord); + assertNotNull(computePattern); + + Matcher matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("DSpace"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Community"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("/wrongpattern/i"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("001"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("?/'`}{][<>.,"); + assertFalse(matcher.matches()); + computePattern = RegexPatternUtils.computePattern(insensitiveWord); + assertNotNull(computePattern); + + matcher = computePattern.matcher("Hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("DSpace"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("Community"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("/wrong-pattern/i"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("001"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("?/'`}{][<>.,"); + assertFalse(matcher.matches()); + } + + @Test + public void testRegexWithoutFlag() { + final String sensitiveWord = "[a-z]+"; + Pattern computePattern = RegexPatternUtils.computePattern(sensitiveWord); + assertNotNull(computePattern); + + Matcher matcher = computePattern.matcher("hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("dspace"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("community"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("DSpace"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Community"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("/wrongpattern/i"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("001"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("?/'`}{][<>.,"); + assertFalse(matcher.matches()); + + final String sensitiveWordWithDelimiter = "/[a-z]+/"; + computePattern = RegexPatternUtils.computePattern(sensitiveWordWithDelimiter); + assertNotNull(computePattern); + + matcher = computePattern.matcher("hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("dspace"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("community"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("DSpace"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Community"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("/wrongpattern/i"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("001"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("?/'`}{][<>.,"); + assertFalse(matcher.matches()); + } + + @Test + public void testWithFuzzyRegex() { + String fuzzyRegex = "/[a-z]+"; + Pattern computePattern = RegexPatternUtils.computePattern(fuzzyRegex); + assertNotNull(computePattern); + + Matcher matcher = computePattern.matcher("/hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + + fuzzyRegex = "[a-z]+/"; + computePattern = RegexPatternUtils.computePattern(fuzzyRegex); + matcher = computePattern.matcher("hello/"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("/hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + + // equals to pattern \\[a-z]+\\ -> searching for a word delimited by '\' + fuzzyRegex = "\\\\[a-z]+\\\\"; + computePattern = RegexPatternUtils.computePattern(fuzzyRegex); + // equals to '\hello\' + matcher = computePattern.matcher("\\hello\\"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("/hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + + // equals to pattern /[a-z]+/ -> searching for a string delimited by '/' + fuzzyRegex = "\\/[a-z]+\\/"; + computePattern = RegexPatternUtils.computePattern(fuzzyRegex); + matcher = computePattern.matcher("/hello/"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("/hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("hello"); + assertFalse(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertFalse(matcher.matches()); + } +} From 279b6785fe5ec9c9ffebff4b22f142da6b3d8deb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 3 Nov 2022 16:05:11 -0500 Subject: [PATCH 052/394] Update to the latest version of all GitHub actions --- .github/workflows/build.yml | 10 +++++----- .github/workflows/docker.yml | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 150a4de1a76b..52714a8ba28f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,18 +45,18 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v2 + uses: actions/checkout@v3 # https://github.com/actions/setup-java - name: Install JDK ${{ matrix.java }} - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: ${{ matrix.java }} distribution: 'temurin' # https://github.com/actions/cache - name: Cache Maven dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 with: # Cache entire ~/.m2/repository path: ~/.m2/repository @@ -74,11 +74,11 @@ jobs: # (This artifact is downloadable at the bottom of any job's summary page) - name: Upload Results of ${{ matrix.type }} to Artifact if: ${{ failure() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: ${{ matrix.type }} results path: ${{ matrix.resultsdir }} # https://github.com/codecov/codecov-action - name: Upload coverage to Codecov.io - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 90372b8d901e..64e12f01aac0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -43,11 +43,11 @@ jobs: steps: # https://github.com/actions/checkout - name: Checkout codebase - uses: actions/checkout@v2 + uses: actions/checkout@v3 # https://github.com/docker/setup-buildx-action - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 # https://github.com/docker/setup-qemu-action - name: Set up QEMU emulation to build for multiple architectures @@ -57,7 +57,7 @@ jobs: - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} @@ -69,7 +69,7 @@ jobs: # Get Metadata for docker_build_deps step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-dependencies' image id: meta_build_deps - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: images: dspace/dspace-dependencies tags: ${{ env.IMAGE_TAGS }} @@ -78,7 +78,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push 'dspace-dependencies' image id: docker_build_deps - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: ./Dockerfile.dependencies @@ -96,7 +96,7 @@ jobs: # Get Metadata for docker_build step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace' image id: meta_build - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} @@ -104,7 +104,7 @@ jobs: - name: Build and push 'dspace' image id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: ./Dockerfile @@ -122,7 +122,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image id: meta_build_test - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: images: dspace/dspace tags: ${{ env.IMAGE_TAGS }} @@ -133,7 +133,7 @@ jobs: - name: Build and push 'dspace-test' image id: docker_build_test - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: ./Dockerfile.test @@ -151,7 +151,7 @@ jobs: # Get Metadata for docker_build_test step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-cli' image id: meta_build_cli - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: images: dspace/dspace-cli tags: ${{ env.IMAGE_TAGS }} @@ -159,7 +159,7 @@ jobs: - name: Build and push 'dspace-cli' image id: docker_build_cli - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: context: . file: ./Dockerfile.cli From d1f61b8acdcc4f959a21eaee886c983d145f468e Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 4 Nov 2022 09:30:18 +0100 Subject: [PATCH 053/394] [8563] [DURACOM-101] Checkstyle fix --- .../src/main/java/org/dspace/app/util/DCInput.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index 7a44bbdd2fdf..bcae3fffb1cb 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -7,8 +7,6 @@ */ package org.dspace.app.util; -import static org.apache.commons.lang3.StringUtils.equalsAnyIgnoreCase; - import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -194,7 +192,7 @@ public DCInput(Map fieldMap, Map> listMap) repeatable = "true".equalsIgnoreCase(repStr) || "yes".equalsIgnoreCase(repStr); String nameVariantsString = fieldMap.get("name-variants"); - nameVariants = (StringUtils.isNotBlank(nameVariantsString)) ? + nameVariants = StringUtils.isNotBlank(nameVariantsString) ? nameVariantsString.equalsIgnoreCase("true") : false; label = fieldMap.get("label"); inputType = fieldMap.get("input-type"); @@ -206,7 +204,7 @@ public DCInput(Map fieldMap, Map> listMap) } hint = fieldMap.get("hint"); warning = fieldMap.get("required"); - required = (warning != null && warning.length() > 0); + required = warning != null && warning.length() > 0; visibility = fieldMap.get("visibility"); readOnly = fieldMap.get("readonly"); vocabulary = fieldMap.get("vocabulary"); @@ -266,7 +264,7 @@ public String extractRegex(Map fieldMap) { * @return whether the input should be displayed or not */ public boolean isVisible(String scope) { - return (visibility == null || visibility.equals(scope)); + return visibility == null || visibility.equals(scope); } /** @@ -399,7 +397,7 @@ public String getLabel() { /** * Get the style for this form field - * + * * @return the style */ public String getStyle() { From 57fcfec276a7b2ad00df9e05066740fdee22bcb1 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 4 Nov 2022 09:34:22 +0100 Subject: [PATCH 054/394] [8563] [DURACOM-101] Changed method name and visibility --- dspace-api/src/main/java/org/dspace/app/util/DCInput.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index bcae3fffb1cb..eb5a51ace279 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -208,7 +208,7 @@ public DCInput(Map fieldMap, Map> listMap) visibility = fieldMap.get("visibility"); readOnly = fieldMap.get("readonly"); vocabulary = fieldMap.get("vocabulary"); - regex = fieldMap.get("regex"); + regex = extractRegexField(fieldMap); String closedVocabularyStr = fieldMap.get("closedVocabulary"); closedVocabulary = "true".equalsIgnoreCase(closedVocabularyStr) || "yes".equalsIgnoreCase(closedVocabularyStr); @@ -239,7 +239,7 @@ public DCInput(Map fieldMap, Map> listMap) } - public String extractRegex(Map fieldMap) { + private String extractRegexField(Map fieldMap) { String regex = fieldMap.get("regex"); Pattern generatedPattern = null; if (regex != null) { From a350090d7606bd72392b40a1ed01a10e22eb9b66 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Mon, 7 Nov 2022 16:52:46 -0600 Subject: [PATCH 055/394] stop requiring Maxmind DatabaseReader to log views statistics --- .../java/org/dspace/statistics/SolrLoggerServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java index 4b2ae94e758e..a9525203854e 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/statistics/SolrLoggerServiceImpl.java @@ -197,7 +197,7 @@ public void post(DSpaceObject dspaceObject, HttpServletRequest request, @Override public void postView(DSpaceObject dspaceObject, HttpServletRequest request, EPerson currentUser) { - if (solr == null || locationService == null) { + if (solr == null) { return; } initSolrYearCores(); @@ -238,7 +238,7 @@ public void postView(DSpaceObject dspaceObject, HttpServletRequest request, @Override public void postView(DSpaceObject dspaceObject, String ip, String userAgent, String xforwardedfor, EPerson currentUser) { - if (solr == null || locationService == null) { + if (solr == null) { return; } initSolrYearCores(); From a152f9baefad6ec1630b5f1ec6d4ba2b67c9cb30 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Wed, 9 Nov 2022 15:53:00 -0600 Subject: [PATCH 056/394] use discovery configuration to get correct sort field type for given sort field --- .../dspace/app/rest/OpenSearchController.java | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java index 6a760b7d0857..b860afc89b53 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java @@ -45,6 +45,8 @@ import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoveryConfigurationService; import org.dspace.discovery.configuration.DiscoverySearchFilter; +import org.dspace.discovery.configuration.DiscoverySortConfiguration; +import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration; import org.dspace.discovery.indexobject.IndexableItem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @@ -141,16 +143,34 @@ public void search(HttpServletRequest request, queryArgs.setStart(start); queryArgs.setMaxResults(count); queryArgs.setDSpaceObjectFilter(IndexableItem.TYPE); + if (sort != null) { - //this is the default sort so we want to switch this to date accessioned - if (sortDirection != null && sortDirection.equals("DESC")) { - queryArgs.setSortField(sort + "_sort", SORT_ORDER.desc); - } else { - queryArgs.setSortField(sort + "_sort", SORT_ORDER.asc); + DiscoveryConfiguration discoveryConfiguration = + searchConfigurationService.getDiscoveryConfiguration(""); + if (discoveryConfiguration != null) { + DiscoverySortConfiguration searchSortConfiguration = discoveryConfiguration.getSearchSortConfiguration(); + if (searchSortConfiguration != null) { + DiscoverySortFieldConfiguration sortFieldConfiguration = searchSortConfiguration + .getSortFieldConfiguration(sort); + if (sortFieldConfiguration != null) { + String sortField = searchService + .toSortFieldIndex(sortFieldConfiguration.getMetadataField(), sortFieldConfiguration.getType()); + + if (sortDirection != null && sortDirection.equals("DESC")) { + queryArgs.setSortField(sortField, SORT_ORDER.desc); + } else { + queryArgs.setSortField(sortField, SORT_ORDER.asc); + } + } else { + throw new IllegalArgumentException(sort + " is not a valid sort field"); + } + } } } else { + // this is the default sort so we want to switch this to date accessioned queryArgs.setSortField("dc.date.accessioned_dt", SORT_ORDER.desc); } + if (dsoObject != null) { container = scopeResolver.resolveScope(context, dsoObject); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService From 196926576d3578d792aa4f5c5d26bab52d5b442e Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 10 Nov 2022 11:43:19 +1300 Subject: [PATCH 057/394] [TLC-387] Add basic unit tests for registered ID handling in S3 bitstore --- .../storage/bitstore/S3BitStoreService.java | 4 ++-- .../bitstore/S3BitStoreServiceTest.java | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) 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 388e34dca509..2bad0ac01267 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 @@ -420,13 +420,13 @@ public String getFullKey(String id) { * @param sInternalId * @return Computed Relative path */ - private String getRelativePath(String sInternalId) { + public String getRelativePath(String sInternalId) { BitstreamStorageService bitstreamStorageService = StorageServiceFactory.getInstance() .getBitstreamStorageService(); String sIntermediatePath = StringUtils.EMPTY; if (bitstreamStorageService.isRegisteredBitstream(sInternalId)) { - sInternalId = sInternalId.substring(2); + sInternalId = sInternalId.substring(REGISTERED_FLAG.length()); } else { sInternalId = sanitizeIdentifier(sInternalId); sIntermediatePath = getIntermediatePath(sInternalId); diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java index e972aaa02b00..920fb9316ccc 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java @@ -10,7 +10,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -63,6 +65,9 @@ public class S3BitStoreServiceTest extends AbstractUnitTest { @Mock private Bitstream bitstream; + @Mock + private Bitstream externalBitstream; + @Before public void setUp() throws Exception { this.s3BitStoreService = new S3BitStoreService(s3Service, tm); @@ -234,6 +239,24 @@ public void givenSubFolderWhenRequestsItemFromS3ThenTheIdentifierShouldHavePrope } + @Test + public void handleRegisteredIdentifierPrefixInS3() { + String trueBitStreamId = "012345"; + String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + trueBitStreamId; + // Should be detected as registered bitstream + assertTrue(this.s3BitStoreService.isRegisteredBitstream(registeredBitstreamId)); + } + + @Test + public void stripRegisteredBitstreamPrefixWhenCalculatingPath() { + // Set paths and IDs + String s3Path = "UNIQUE_S3_PATH/test/bitstream.pdf"; + String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + s3Path; + // Paths should be equal, since the getRelativePath method should strip the registered -R prefix + String relativeRegisteredPath = this.s3BitStoreService.getRelativePath(registeredBitstreamId); + assertEquals(s3Path, relativeRegisteredPath); + } + @Test public void givenBitStreamIdentifierLongerThanPossibleWhenIntermediatePathIsComputedThenIsSplittedAndTruncated() { String path = "01234567890123456789"; From 99ecf531ea5f7bfa4c83c3934a7295e42616b9d0 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Thu, 10 Nov 2022 08:02:25 -0600 Subject: [PATCH 058/394] fix checkstyle errors --- .../main/java/org/dspace/app/rest/OpenSearchController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java index b860afc89b53..79ca3817534d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/OpenSearchController.java @@ -148,13 +148,15 @@ public void search(HttpServletRequest request, DiscoveryConfiguration discoveryConfiguration = searchConfigurationService.getDiscoveryConfiguration(""); if (discoveryConfiguration != null) { - DiscoverySortConfiguration searchSortConfiguration = discoveryConfiguration.getSearchSortConfiguration(); + DiscoverySortConfiguration searchSortConfiguration = discoveryConfiguration + .getSearchSortConfiguration(); if (searchSortConfiguration != null) { DiscoverySortFieldConfiguration sortFieldConfiguration = searchSortConfiguration .getSortFieldConfiguration(sort); if (sortFieldConfiguration != null) { String sortField = searchService - .toSortFieldIndex(sortFieldConfiguration.getMetadataField(), sortFieldConfiguration.getType()); + .toSortFieldIndex(sortFieldConfiguration.getMetadataField(), + sortFieldConfiguration.getType()); if (sortDirection != null && sortDirection.equals("DESC")) { queryArgs.setSortField(sortField, SORT_ORDER.desc); From 29b1eaa078e6d1c84bc34bda84023959c78fc396 Mon Sep 17 00:00:00 2001 From: nwoodward Date: Thu, 10 Nov 2022 11:49:45 -0600 Subject: [PATCH 059/394] added sort tests --- .../opensearch/OpenSearchControllerIT.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java index 9974d7e725f2..ac03e946e320 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/opensearch/OpenSearchControllerIT.java @@ -184,6 +184,32 @@ public void emptyQueryTest() throws Exception { ; } + @Test + public void validSortTest() throws Exception { + //When we call the root endpoint + getClient().perform(get("/opensearch/search") + .param("query", "") + .param("sort", "dc.date.issued")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/atom+xml;charset=UTF-8" + .andExpect(content().contentType("application/atom+xml;charset=UTF-8")) + .andExpect(xpath("feed/totalResults").string("0")) + ; + } + + @Test + public void invalidSortTest() throws Exception { + //When we call the root endpoint + getClient().perform(get("/opensearch/search") + .param("query", "") + .param("sort", "dc.invalid.field")) + //We get an exception for such a sort field + //The status has to be 400 ERROR + .andExpect(status().is(400)) + ; + } + @Test public void serviceDocumentTest() throws Exception { //When we call the root endpoint From ee1f9f047ea1d395ee21cc4af4bf6b04811bc514 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 11 Nov 2022 16:13:08 -0600 Subject: [PATCH 060/394] Require javadocs for all public methods. Fix checkstyle errors. --- checkstyle.xml | 4 +--- .../java/org/dspace/external/OpenAIRERestConnector.java | 2 +- .../external/provider/impl/LiveImportDataProvider.java | 2 +- .../service/BibtexImportMetadataSourceServiceImpl.java | 4 ++-- .../external/liveimportclient/service/LiveImportClient.java | 2 +- .../contributor/EpoIdMetadataContributor.java | 2 +- .../contributor/SimpleJsonPathMetadataContributor.java | 2 +- .../ris/service/RisImportMetadataSourceServiceImpl.java | 4 ++-- .../service/components/AbstractPlainMetadataSource.java | 6 +++--- .../importer/external/service/components/FileSource.java | 4 ++-- dspace-api/src/main/java/org/dspace/scripts/Process.java | 6 +++--- .../dspace/app/rest/security/RestAuthenticationService.java | 2 +- .../org/dspace/app/rest/matcher/ClaimedTaskMatcher.java | 4 ++-- .../java/org/dspace/app/rest/matcher/PoolTaskMatcher.java | 2 +- .../src/main/java/org/dspace/services/RequestService.java | 2 +- 15 files changed, 23 insertions(+), 25 deletions(-) diff --git a/checkstyle.xml b/checkstyle.xml index 815edaec7bf0..e0fa808d83cb 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -92,9 +92,7 @@ For more information on CheckStyle configurations below, see: http://checkstyle. - - - + diff --git a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java index 5e45d6324d2c..b0aa4aba13a9 100644 --- a/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java +++ b/dspace-api/src/main/java/org/dspace/external/OpenAIRERestConnector.java @@ -335,7 +335,7 @@ public void setClientSecret(String clientSecret) { /** * tokenUsage true to enable the usage of an access token * - * @param tokenUsage + * @param tokenEnabled true/false */ @Autowired(required = false) public void setTokenEnabled(boolean tokenEnabled) { diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java index 962183fa6f85..2e934462c9f1 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java @@ -57,7 +57,7 @@ public void setSourceIdentifier(String sourceIdentifier) { /** * This method set the MetadataSource for the ExternalDataProvider - * @param metadataSource {@link org.dspace.importer.external.service.components.MetadataSource} implementation used to process the input data + * @param querySource Source {@link org.dspace.importer.external.service.components.QuerySource} implementation used to process the input data */ public void setMetadataSource(QuerySource querySource) { this.querySource = querySource; diff --git a/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java index 445dfedebdc6..0014088c8650 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java @@ -105,10 +105,10 @@ private BibTeXDatabase parseBibTex(InputStream inputStream) throws IOException, /** - * Retrieve the MetadataFieldMapping containing the mapping between RecordType + * Set the MetadataFieldMapping containing the mapping between RecordType * (in this case PlainMetadataSourceDto.class) and Metadata * - * @return The configured MetadataFieldMapping + * @param metadataFieldMap The configured MetadataFieldMapping */ @Override @SuppressWarnings("unchecked") diff --git a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java index 829b5ed2de13..a1132cda9ce2 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/liveimportclient/service/LiveImportClient.java @@ -21,7 +21,7 @@ public interface LiveImportClient { * * @param timeout The connect timeout in milliseconds * @param URL URL - * @param requestParams This map contains the parameters to be included in the request. + * @param params This map contains the parameters to be included in the request. * Each parameter will be added to the url?(key=value) * @return The response in String type converted from InputStream */ diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java index 00b414c4859a..e32f45a4d5f3 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/EpoIdMetadataContributor.java @@ -156,7 +156,7 @@ public void setQuery(String query) { * Depending on the retrieved node (using the query), different types of values will be added to the MetadatumDTO * list * - * @param t A class to retrieve metadata from. + * @param element A class to retrieve metadata from. * @return a collection of import records. Only the identifier of the found records may be put in the record. */ @Override diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java index f7399802200e..590fc63283b9 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleJsonPathMetadataContributor.java @@ -118,7 +118,7 @@ public void setMetadataProcessor(JsonPathMetadataProcessor metadataProcessor) { * Retrieve the metadata associated with the given object. * The toString() of the resulting object will be used. * - * @param t A class to retrieve metadata from. + * @param fullJson A class to retrieve metadata from. * @return a collection of import records. Only the identifier of the found records may be put in the record. */ @Override diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java index 2574e187dfc6..1f460c19e697 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/ris/service/RisImportMetadataSourceServiceImpl.java @@ -126,10 +126,10 @@ private List notAggregatedData(InputStream inputStrea } /** - * Retrieve the MetadataFieldMapping containing the mapping between RecordType + * Set the MetadataFieldMapping containing the mapping between RecordType * (in this case PlainMetadataSourceDto.class) and Metadata * - * @return The configured MetadataFieldMapping + * @param metadataFieldMap The configured MetadataFieldMapping */ @Override @SuppressWarnings("unchecked") diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java index 019cf33177c2..5d83b9a7cce4 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/AbstractPlainMetadataSource.java @@ -42,7 +42,7 @@ public abstract class AbstractPlainMetadataSource /** * Set the file extensions supported by this metadata service * - * @param supportedExtensionsthe file extensions (xml,txt,...) supported by this service + * @param supportedExtensions the file extensions (xml,txt,...) supported by this service */ public void setSupportedExtensions(List supportedExtensions) { this.supportedExtensions = supportedExtensions; @@ -57,7 +57,7 @@ public List getSupportedExtensions() { * Return a list of ImportRecord constructed from input file. This list is based on * the results retrieved from the file (InputStream) parsed through abstract method readData * - * @param InputStream The inputStream of the file + * @param is The inputStream of the file * @return A list of {@link ImportRecord} * @throws FileSourceException if, for any reason, the file is not parsable */ @@ -76,7 +76,7 @@ public List getRecords(InputStream is) throws FileSourceException * the result retrieved from the file (InputStream) parsed through abstract method * "readData" implementation * - * @param InputStream The inputStream of the file + * @param is The inputStream of the file * @return An {@link ImportRecord} matching the file content * @throws FileSourceException if, for any reason, the file is not parsable * @throws FileMultipleOccurencesException if the file contains more than one entry diff --git a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java index 5bef0984df7f..13c81d15162b 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/service/components/FileSource.java @@ -30,7 +30,7 @@ public interface FileSource extends MetadataSource { /** * Return a list of ImportRecord constructed from input file. * - * @param InputStream The inputStream of the file + * @param inputStream The inputStream of the file * @return A list of {@link ImportRecord} * @throws FileSourceException if, for any reason, the file is not parsable */ @@ -40,7 +40,7 @@ public List getRecords(InputStream inputStream) /** * Return an ImportRecord constructed from input file. * - * @param InputStream The inputStream of the file + * @param inputStream The inputStream of the file * @return An {@link ImportRecord} matching the file content * @throws FileSourceException if, for any reason, the file is not parsable * @throws FileMultipleOccurencesException if the file contains more than one entry diff --git a/dspace-api/src/main/java/org/dspace/scripts/Process.java b/dspace-api/src/main/java/org/dspace/scripts/Process.java index 15cc1b2fc68a..ce41f46bdf7b 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -225,15 +225,15 @@ public Date getCreationTime() { } /** - * This method sets the special groups associated with the Process. + * This method will return the special groups associated with the Process. */ public List getGroups() { return groups; } /** - * This method will return special groups associated with the Process. - * @return The special groups of this process. + * This method sets the special groups associated with the Process. + * @param groups The special groups of this process. */ public void setGroups(List groups) { this.groups = groups; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java index 6b106d1b7799..dd464edfcdee 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestAuthenticationService.java @@ -53,7 +53,7 @@ void addAuthenticationDataForUser(HttpServletRequest request, HttpServletRespons * Checks the current request for a valid authentication token. If found, extracts that token and obtains the * currently logged in EPerson. * @param request current request - * @param request current response + * @param response current response * @param context current DSpace Context * @return EPerson of the logged in user (if auth token found), or null if no auth token is found */ diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java index 0d2ba67f16be..4ab812f82cd4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ClaimedTaskMatcher.java @@ -28,8 +28,8 @@ private ClaimedTaskMatcher() { } /** * Check if the returned json expose all the required links and properties * - * @param ptask - * the pool task + * @param cTask + * the claimed task * @param step * the step name * @return diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java index aeed70e52774..8d43acbb0fd0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/PoolTaskMatcher.java @@ -28,7 +28,7 @@ private PoolTaskMatcher() { } /** * Check if the returned json expose all the required links and properties * - * @param ptask + * @param pTask * the pool task * @param step * the step name diff --git a/dspace-services/src/main/java/org/dspace/services/RequestService.java b/dspace-services/src/main/java/org/dspace/services/RequestService.java index 0c0c5fad4508..d28a04ecf623 100644 --- a/dspace-services/src/main/java/org/dspace/services/RequestService.java +++ b/dspace-services/src/main/java/org/dspace/services/RequestService.java @@ -111,7 +111,7 @@ public interface RequestService { /** * Set the ID of the current authenticated user * - * @return the id of the user associated with the current thread OR null if there is no user + * @param epersonId the id of the user associated with the current thread OR null if there is no user */ public void setCurrentUserId(UUID epersonId); From 59838e6e29d06ddf0b662a0b38930f0cc68f34c0 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 15 Nov 2022 16:17:51 -0600 Subject: [PATCH 061/394] Add link to REST Contract from PR template --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b11e3cd531cf..e96bb214eb37 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -23,4 +23,4 @@ _This checklist provides a reminder of what we are going to look for when review - [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods. - [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). - [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. -- [ ] If my PR modifies the REST API, I've linked to the REST Contract page (or open PR) related to this change. +- [ ] If my PR modifies the REST API, I've opened a separate [REST Contract]([https://github.com/DSpace/RestContract](https://github.com/DSpace/RestContract/blob/main/README.md)) PR related to this change. From f2fa6863d26a4fb70ecba8f786e80769ca805636 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 15 Nov 2022 16:18:35 -0600 Subject: [PATCH 062/394] Fix broken link in PR template --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e96bb214eb37..ea5c53564741 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -23,4 +23,4 @@ _This checklist provides a reminder of what we are going to look for when review - [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods. - [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). - [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. -- [ ] If my PR modifies the REST API, I've opened a separate [REST Contract]([https://github.com/DSpace/RestContract](https://github.com/DSpace/RestContract/blob/main/README.md)) PR related to this change. +- [ ] If my PR modifies the REST API, I've opened a separate [REST Contract](https://github.com/DSpace/RestContract/blob/main/README.md) PR related to this change. From ffc15ec54a8048ee672fcfd9c2da967f1b2a0258 Mon Sep 17 00:00:00 2001 From: Adan Roman Date: Wed, 16 Nov 2022 16:22:36 +0100 Subject: [PATCH 063/394] #8412 In Submission Form, list collections alphabetically --- .../app/rest/repository/CollectionRestRepository.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index ba3163a4447c..8f5150ae9296 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.sql.SQLException; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -45,6 +46,7 @@ import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.Item; +import org.dspace.content.comparator.NameAscendingComparator; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -185,6 +187,7 @@ public Page findSubmitAuthorizedByCommunity( List collections = cs.findCollectionsWithSubmit(q, context, com, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); + Collections.sort(collections, new NameAscendingComparator()); int tot = cs.countCollectionsWithSubmit(q, context, com); return converter.toRestPage(collections, pageable, tot , utils.obtainProjection()); } catch (SQLException | SearchServiceException e) { @@ -200,6 +203,7 @@ public Page findSubmitAuthorized(@Parameter(value = "query") Str List collections = cs.findCollectionsWithSubmit(q, context, null, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); + Collections.sort(collections, new NameAscendingComparator()); int tot = cs.countCollectionsWithSubmit(q, context, null); return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); } catch (SQLException e) { @@ -216,6 +220,7 @@ public Page findAdminAuthorized ( List collections = authorizeService.findAdminAuthorizedCollection(context, query, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); + Collections.sort(collections, new NameAscendingComparator()); long tot = authorizeService.countAdminAuthorizedCollection(context, query); return converter.toRestPage(collections, pageable, tot , utils.obtainProjection()); } catch (SearchServiceException | SQLException e) { @@ -248,6 +253,7 @@ public Page findSubmitAuthorizedByEntityType( List collections = cs.findCollectionsWithSubmit(query, context, null, entityTypeLabel, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); + Collections.sort(collections, new NameAscendingComparator()); int tot = cs.countCollectionsWithSubmit(query, context, null, entityTypeLabel); return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); } catch (SQLException e) { @@ -285,6 +291,7 @@ public Page findSubmitAuthorizedByCommunityAndEntityType( List collections = cs.findCollectionsWithSubmit(query, context, community, entityTypeLabel, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); + Collections.sort(collections, new NameAscendingComparator()); int total = cs.countCollectionsWithSubmit(query, context, community, entityTypeLabel); return converter.toRestPage(collections, pageable, total, utils.obtainProjection()); } catch (SQLException | SearchServiceException e) { From 5308fd9ff7388c1ead7d41291b9fb6976441fb66 Mon Sep 17 00:00:00 2001 From: eskander Date: Thu, 17 Nov 2022 18:51:42 +0200 Subject: [PATCH 064/394] [DSC-670] process to export current cris-layout-configuration --- .../factory/CrisLayoutServiceFactory.java | 3 + .../impl/CrisLayoutServiceFactoryImpl.java | 9 + ...tCrisLayoutToolCliScriptConfiguration.java | 18 + .../script/ExportCrisLayoutToolScript.java | 116 ++++++ .../script/ExportCrisLayoutToolScriptCli.java | 18 + ...portCrisLayoutToolScriptConfiguration.java | 58 +++ .../service/CrisLayoutToolConverter.java | 30 ++ .../service/CrisLayoutToolValidator.java | 10 + .../impl/CrisLayoutToolConverterImpl.java | 334 ++++++++++++++++++ .../cris-layout-configuration-template.xls | Bin 0 -> 686080 bytes .../export-valid-layout-with-3-tabs.xls | Bin 0 -> 20992 bytes .../config/spring/api/scripts.xml | 7 +- .../script/ExportCrisLayoutToolScriptIT.java | 191 ++++++++++ dspace/config/spring/api/core-services.xml | 1 + dspace/config/spring/api/scripts.xml | 7 +- dspace/config/spring/rest/scripts.xml | 7 +- 16 files changed, 806 insertions(+), 3 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolCliScriptConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScript.java create mode 100644 dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptCli.java create mode 100644 dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolConverter.java create mode 100644 dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java create mode 100644 dspace-api/src/main/resources/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls create mode 100644 dspace-api/src/test/data/dspaceFolder/assetstore/layout/script/export-valid-layout-with-3-tabs.xls create mode 100644 dspace-api/src/test/java/org/dspace/layout/script/ExportCrisLayoutToolScriptIT.java diff --git a/dspace-api/src/main/java/org/dspace/layout/factory/CrisLayoutServiceFactory.java b/dspace-api/src/main/java/org/dspace/layout/factory/CrisLayoutServiceFactory.java index 1f9e3f5960bd..1b2bb304c857 100644 --- a/dspace-api/src/main/java/org/dspace/layout/factory/CrisLayoutServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/layout/factory/CrisLayoutServiceFactory.java @@ -7,6 +7,7 @@ */ package org.dspace.layout.factory; +import org.dspace.layout.script.service.CrisLayoutToolConverter; import org.dspace.layout.script.service.CrisLayoutToolParser; import org.dspace.layout.script.service.CrisLayoutToolValidator; import org.dspace.layout.service.CrisLayoutBoxService; @@ -46,4 +47,6 @@ public static CrisLayoutServiceFactory getInstance() { public abstract CrisLayoutToolParser getCrisLayoutToolParser(); + public abstract CrisLayoutToolConverter getCrisLayoutToolConverter(); + } diff --git a/dspace-api/src/main/java/org/dspace/layout/factory/impl/CrisLayoutServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/layout/factory/impl/CrisLayoutServiceFactoryImpl.java index 8a82f7586c0a..89eb36da6f4d 100644 --- a/dspace-api/src/main/java/org/dspace/layout/factory/impl/CrisLayoutServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/layout/factory/impl/CrisLayoutServiceFactoryImpl.java @@ -8,6 +8,7 @@ package org.dspace.layout.factory.impl; import org.dspace.layout.factory.CrisLayoutServiceFactory; +import org.dspace.layout.script.service.CrisLayoutToolConverter; import org.dspace.layout.script.service.CrisLayoutToolParser; import org.dspace.layout.script.service.CrisLayoutToolValidator; import org.dspace.layout.service.CrisLayoutBoxService; @@ -50,6 +51,9 @@ public class CrisLayoutServiceFactoryImpl extends CrisLayoutServiceFactory { @Autowired(required = true) private CrisLayoutToolParser parser; + @Autowired(required = true) + private CrisLayoutToolConverter converter; + @Override public CrisLayoutTabService getTabService() { return this.tabService; @@ -90,4 +94,9 @@ public CrisLayoutToolParser getCrisLayoutToolParser() { return parser; } + @Override + public CrisLayoutToolConverter getCrisLayoutToolConverter() { + return converter; + } + } diff --git a/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolCliScriptConfiguration.java new file mode 100644 index 000000000000..0b672e6b46ec --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolCliScriptConfiguration.java @@ -0,0 +1,18 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.layout.script; + +/** + * Extension of {@link ExportCrisLayoutToolScript} for CLI. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class ExportCrisLayoutToolCliScriptConfiguration + extends ExportCrisLayoutToolScriptConfiguration { + +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScript.java b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScript.java new file mode 100644 index 000000000000..afff6782852a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScript.java @@ -0,0 +1,116 @@ +/** + * 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.layout.script; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.sql.SQLException; +import java.util.UUID; + +import org.apache.commons.cli.ParseException; +import org.apache.poi.ss.usermodel.Workbook; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.layout.factory.CrisLayoutServiceFactory; +import org.dspace.layout.script.service.CrisLayoutToolConverter; +import org.dspace.layout.service.CrisLayoutTabService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.utils.DSpace; + +/** + * Script to export CRIS layout configuration into excel file. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class ExportCrisLayoutToolScript + extends DSpaceRunnable> { + + private AuthorizeService authorizeService; + + private CrisLayoutTabService tabService; + + private CrisLayoutToolConverter converter; + + private Context context; + + @Override + public void setup() throws ParseException { + this.authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); + this.converter = CrisLayoutServiceFactory.getInstance().getCrisLayoutToolConverter(); + this.tabService = CrisLayoutServiceFactory.getInstance().getTabService(); + } + + @Override + public void internalRun() throws Exception { + + context = new Context(); + assignCurrentUserInContext(); + assignSpecialGroupsInContext(); + + context.turnOffAuthorisationSystem(); + + if (!this.authorizeService.isAdmin(context)) { + throw new IllegalArgumentException("The user cannot use the cris layout configuration tool"); + } + + try { + performExport(); + context.complete(); + handler.logInfo("Export has completed successfully"); + } catch (Exception e) { + handler.handleException(e); + context.abort(); + } finally { + context.restoreAuthSystemState(); + } + } + + private void performExport() throws Exception { + Workbook workbook = converter.convert(tabService.findAll(context)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + workbook.write(out); + + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + handler.writeFilestream(context, getFileName(), in, getMIMEType()); + + handler.logInfo("Layout exported successfully into file named " + getFileName()); + } + + @Override + @SuppressWarnings("unchecked") + public ExportCrisLayoutToolScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("export-cris-layout-tool", + ExportCrisLayoutToolScriptConfiguration.class); + } + + private void assignCurrentUserInContext() throws SQLException { + UUID uuid = getEpersonIdentifier(); + if (uuid != null) { + EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid); + context.setCurrentUser(ePerson); + } + } + + private void assignSpecialGroupsInContext() { + for (UUID uuid : handler.getSpecialGroups()) { + context.setSpecialGroup(uuid); + } + } + + private String getFileName() { + return "cris-layout-tool-exported.xls"; + } + + public String getMIMEType() { + return "application/vnd.ms-excel"; + } +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptCli.java b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptCli.java new file mode 100644 index 000000000000..daadae29abc0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptCli.java @@ -0,0 +1,18 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.layout.script; + +/** + * Extension of {@link ExportCrisLayoutToolScript} for CLI. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class ExportCrisLayoutToolScriptCli extends ExportCrisLayoutToolScript{ + +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptConfiguration.java new file mode 100644 index 000000000000..0be873b57985 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/ExportCrisLayoutToolScriptConfiguration.java @@ -0,0 +1,58 @@ +/** + * 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.layout.script; + +import java.sql.SQLException; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Configuration for {@link ExportCrisLayoutToolScript}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class ExportCrisLayoutToolScriptConfiguration + extends ScriptConfiguration { + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (options == null) { + options = new Options(); + } + return options; + } + + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolConverter.java b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolConverter.java new file mode 100644 index 000000000000..b57f4f35523c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolConverter.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.layout.script.service; + +import java.util.List; + +import org.apache.poi.ss.usermodel.Workbook; +import org.dspace.layout.CrisLayoutTab; + +/** + * Cris layout configuration tool converter. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public interface CrisLayoutToolConverter { + + /** + * convert the given list of tabs to workbook. + * + * @param tabs the tabs to convert + * @return the workbook + */ + Workbook convert(List tabs); +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java index 96d04fd7e087..c1a9cff5dbb4 100644 --- a/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java +++ b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java @@ -37,6 +37,16 @@ public interface CrisLayoutToolValidator { String BOX_POLICY_SHEET = "boxpolicy"; + String UTILSDATA_SHEET = "utilsdata"; + + String TAB_i18n_SHEET = "tab_i18n"; + + String BOX_i18n_SHEET = "box_i18n"; + + String METADATA_i18n_SHEET = "metadata_i18n"; + + String METADATAGROUP_i18n_SHEET = "metadatagroup_i18n"; + String CONTAINER_COLUMN = "CONTAINER"; diff --git a/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java b/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java new file mode 100644 index 000000000000..1aec1c349372 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java @@ -0,0 +1,334 @@ +/** + * 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.layout.script.service.impl; + +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METADATA_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METRICS_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX_POLICY_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.METADATAGROUPS_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.METADATAGROUP_TYPE; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB2BOX_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB_POLICY_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB_SHEET; +import static org.dspace.util.WorkbookUtils.createCell; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.dspace.content.MetadataField; +import org.dspace.eperson.Group; +import org.dspace.layout.CrisLayoutBox; +import org.dspace.layout.CrisLayoutCell; +import org.dspace.layout.CrisLayoutField; +import org.dspace.layout.CrisLayoutFieldBitstream; +import org.dspace.layout.CrisLayoutMetric2Box; +import org.dspace.layout.CrisLayoutTab; +import org.dspace.layout.CrisMetadataGroup; +import org.dspace.layout.LayoutSecurity; +import org.dspace.layout.script.service.CrisLayoutToolConverter; + +/** + * Implementation of {@link CrisLayoutToolConverter}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class CrisLayoutToolConverterImpl implements CrisLayoutToolConverter { + + @Override + public Workbook convert(List tabs) { + Workbook workbook = getTemplateWorkBook(); + buildTab(workbook, tabs); + autoSizeAllSheetsColumns(workbook); + return workbook; + } + + private Workbook getTemplateWorkBook() { + try (InputStream inputStream = + CrisLayoutToolConverterImpl.class + .getResourceAsStream("cris-layout-configuration-template.xls")) { + return WorkbookFactory.create(inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void buildTab(Workbook workbook, List tabs) { + Sheet sheet = workbook.getSheet(TAB_SHEET); + tabs.forEach(tab -> { + buildTabRow(sheet, tab); + buildTab2box(workbook, tab); + buildTabPolicy(workbook, tab); + }); + } + + private void buildTabRow(Sheet sheet, CrisLayoutTab tab) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, tab.getEntity().getLabel()); + createCell(row, 1, tab.getShortName()); + createCell(row, 2, tab.getHeader()); + createCell(row, 3, String.valueOf(tab.getPriority())); + createCell(row, 4, convertToString(tab.isLeading())); + createCell(row, 5, toSecurity(tab.getSecurity())); + } + + private void buildTab2box(Workbook workbook, CrisLayoutTab tab) { + Sheet sheet = workbook.getSheet(TAB2BOX_SHEET); + for (int i = 0 ; i < tab.getRows().size() ; i++) { + // position column into database starts from 0, so will increase 1 + int rowIndex = i + 1; + tab.getRows().get(i).getCells() + .forEach(cell -> { + buildTab2boxRow(sheet, rowIndex, cell); + buildBox(sheet.getWorkbook(), cell.getBoxes()); + buildBoxPolicy(sheet.getWorkbook(), cell.getBoxes()); + }); + } + } + + private void buildTab2boxRow(Sheet sheet, int cellIndex, CrisLayoutCell cell) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, cell.getRow().getTab().getEntity().getLabel()); + createCell(row, 1, cell.getRow().getTab().getShortName()); + createCell(row, 2, String.valueOf(cellIndex)); + createCell(row, 3, cell.getRow().getStyle()); + createCell(row, 4, cell.getStyle()); + createCell(row, 5, getBoxesNames(cell.getBoxes())); + } + + private String getBoxesNames(List boxes) { + return boxes.stream() + .map(box -> box.getShortname()) + .collect(Collectors.joining(", ")); + } + + private void buildBox(Workbook workbook, List boxes) { + Sheet sheet = workbook.getSheet(BOX_SHEET); + boxes.forEach(box -> { + buildBoxRow(sheet, box); + buildBox2metadata(sheet.getWorkbook(), box.getLayoutFields()); + buildBox2metrics(sheet.getWorkbook(), box); + }); + } + + private void buildBoxRow(Sheet sheet, CrisLayoutBox box) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, box.getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, convertToString(box.getCollapsed())); + createCell(row, 2, box.getType()); + createCell(row, 3, box.getShortname()); + createCell(row, 4, box.getHeader()); + createCell(row, 5, convertToString(box.isContainer())); + createCell(row, 6, convertToString(box.getMinor())); + createCell(row, 7, toSecurity(box.getSecurity())); + createCell(row, 8, box.getStyle()); + } + + private void buildBox2metadata(Workbook workbook, List layoutFields) { + Sheet sheet = workbook.getSheet(BOX2METADATA_SHEET); + layoutFields.forEach(layoutField -> { + buildBox2metadataRow(sheet, layoutField); + buildMetadataGroups(sheet.getWorkbook(), layoutField.getCrisMetadataGroupList()); + }); + } + + private void buildBox2metadataRow(Sheet sheet, CrisLayoutField layoutField) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, layoutField.getBox().getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, layoutField.getBox().getShortname()); + createCell(row, 2, String.valueOf(layoutField.getRow())); + createCell(row, 3, String.valueOf(layoutField.getCell())); + createCell(row, 4, getLayoutFieldType(layoutField)); + createCell(row, 5, getMetadataField(layoutField)); + createCell(row, 6, getMetadataValue(layoutField)); + createCell(row, 7, getBundle(layoutField)); + createCell(row, 8, layoutField.getLabel()); + createCell(row, 9, convertToString(layoutField.isLabelAsHeading())); + createCell(row, 10, layoutField.getRendering()); + createCell(row, 11, convertToString(layoutField.isValuesInline())); + createCell(row, 12, layoutField.getRowStyle()); + createCell(row, 13, layoutField.getCellStyle()); + createCell(row, 14, layoutField.getStyleLabel()); + createCell(row, 15, layoutField.getStyleValue()); + } + + private String getMetadataValue(CrisLayoutField layoutField) { + String value = ""; + if (layoutField instanceof CrisLayoutFieldBitstream) { + value = ((CrisLayoutFieldBitstream) layoutField).getMetadataValue(); + } + return value; + } + + private String getBundle(CrisLayoutField layoutField) { + String value = ""; + if (layoutField instanceof CrisLayoutFieldBitstream) { + value = ((CrisLayoutFieldBitstream) layoutField).getBundle(); + } + return value; + } + + private String getMetadataField(CrisLayoutField layoutField) { + return Optional.ofNullable(layoutField.getMetadataField()) + .map(metadataField -> metadataField.toString('.')) + .orElse(""); + } + + private String getLayoutFieldType(CrisLayoutField layoutField) { + String type = layoutField.getType(); + if (CollectionUtils.isNotEmpty(layoutField.getCrisMetadataGroupList())) { + type = METADATAGROUP_TYPE; + } + return type; + } + + private void buildMetadataGroups(Workbook workbook, List crisMetadataGroups) { + Sheet sheet = workbook.getSheet(METADATAGROUPS_SHEET); + crisMetadataGroups + .forEach(crisMetadataGroup -> + buildMetadataGroupRow(sheet, crisMetadataGroup)); + } + + private void buildMetadataGroupRow(Sheet sheet, CrisMetadataGroup crisMetadataGroup) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + CrisLayoutField crisLayoutField = crisMetadataGroup.getCrisLayoutField(); + + createCell(row, 0, crisLayoutField.getBox().getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, crisLayoutField.getMetadataField().toString('.')); + createCell(row, 2, crisLayoutField.getType()); + createCell(row, 3, crisMetadataGroup.getMetadataField().toString('.')); + createCell(row, 4, ""); + createCell(row, 5, ""); + createCell(row, 6, crisMetadataGroup.getLabel()); + createCell(row, 7, crisMetadataGroup.getRendering()); + createCell(row, 8, crisMetadataGroup.getStyleLabel()); + createCell(row, 9, crisMetadataGroup.getStyleValue()); + } + + private void buildBox2metrics(Workbook workbook, CrisLayoutBox box) { + Sheet sheet = workbook.getSheet(BOX2METRICS_SHEET); + buildBox2metricsRow(sheet, box); + } + + private void buildBox2metricsRow(Sheet sheet, CrisLayoutBox box) { + if (CollectionUtils.isNotEmpty(box.getMetric2box())) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, box.getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, box.getShortname()); + createCell(row, 2, getMetric2BoxTypes(box.getMetric2box())); + } + } + + private String getMetric2BoxTypes(List crisLayoutMetric2Boxes) { + return crisLayoutMetric2Boxes.stream() + .map(CrisLayoutMetric2Box::getType) + .collect(Collectors.joining(", ")); + } + + private void buildTabPolicy(Workbook workbook, CrisLayoutTab tab) { + Sheet sheet = workbook.getSheet(TAB_POLICY_SHEET); + tab.getMetadataSecurityFields() + .forEach(metadataField -> + buildTabPolicyMetadataSecurityFieldRow(sheet, tab, metadataField) + ); + + tab.getGroupSecurityFields() + .forEach(group -> + buildTabPolicyGroupSecurityFieldRow(sheet, tab, group) + ); + } + + private void buildTabPolicyMetadataSecurityFieldRow(Sheet sheet, CrisLayoutTab tab, MetadataField metadataField) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, tab.getEntity().getLabel()); + createCell(row, 1, tab.getShortName()); + createCell(row, 2, metadataField.toString('.')); + createCell(row, 3, ""); + } + + private void buildTabPolicyGroupSecurityFieldRow(Sheet sheet, CrisLayoutTab tab, Group group) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, tab.getEntity().getLabel()); + createCell(row, 1, tab.getShortName()); + createCell(row, 2, ""); + createCell(row, 3, group.getName()); + } + + private void buildBoxPolicy(Workbook workbook, List boxes) { + Sheet sheet = workbook.getSheet(BOX_POLICY_SHEET); + boxes.forEach(box -> { + box.getMetadataSecurityFields() + .forEach(metadataField -> + buildBoxPolicyMetadataSecurityFieldRow(sheet, box, metadataField) + ); + + box.getGroupSecurityFields() + .forEach(group -> + buildBoxPolicyGroupSecurityFieldRow(sheet, box, group) + ); + }); + } + + private void buildBoxPolicyMetadataSecurityFieldRow(Sheet sheet, CrisLayoutBox box, MetadataField metadataField) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, box.getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, box.getShortname()); + createCell(row, 2, metadataField.toString('.')); + createCell(row, 3, ""); + } + + private void buildBoxPolicyGroupSecurityFieldRow(Sheet sheet, CrisLayoutBox box, Group group) { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + createCell(row, 0, box.getCell().getRow().getTab().getEntity().getLabel()); + createCell(row, 1, box.getShortname()); + createCell(row, 2, ""); + createCell(row, 3, group.getName()); + } + + private String convertToString(boolean value) { + return value ? "y" : "n"; + } + + private String toSecurity(Integer security) { + return String.valueOf(LayoutSecurity.valueOf(security)) + .replaceAll("_", " ") + .replaceAll("AND", "&"); + } + + private void autoSizeAllSheetsColumns(Workbook workbook) { + autoSizeColumns(workbook.getSheet(TAB_SHEET)); + autoSizeColumns(workbook.getSheet(TAB2BOX_SHEET)); + autoSizeColumns(workbook.getSheet(BOX_SHEET)); + autoSizeColumns(workbook.getSheet(BOX2METADATA_SHEET)); + autoSizeColumns(workbook.getSheet(METADATAGROUPS_SHEET)); + autoSizeColumns(workbook.getSheet(BOX2METRICS_SHEET)); + autoSizeColumns(workbook.getSheet(TAB_POLICY_SHEET)); + autoSizeColumns(workbook.getSheet(BOX_POLICY_SHEET)); + } + + private void autoSizeColumns(Sheet sheet) { + if (sheet.getPhysicalNumberOfRows() > 0) { + Row row = sheet.getRow(sheet.getFirstRowNum()); + for (Cell cell : row) { + int columnIndex = cell.getColumnIndex(); + sheet.autoSizeColumn(columnIndex); + } + } + } + +} diff --git a/dspace-api/src/main/resources/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls b/dspace-api/src/main/resources/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls new file mode 100644 index 0000000000000000000000000000000000000000..a921219abfad173dde9253ce4368468386eda6c7 GIT binary patch literal 686080 zcmeF)1(;Ul7C!tvbO{)UEn;_ff}o-Z2-tzBpeUjuc42pScXxMpcXzj9w|w{g&ah^! z@pc}U-#P#9yZ+aibC#@kF|*c>;oW;b`d%Sot2VCQ z5&6=s8wy>kYDwgEc=+eUi4!Ni+ju1VBKAYrKa;UjzCPd)~}j;(zJe&Zj$O^s)ae;WUj5A)}XfAapN_xa!a#?L_M zbH(5Hpa1sla1^&`UEMy}6FlT=$Nsxji|U$I_u{`*^&Vm)JkxM)Sb6y9&3pWxdh1;? z46mc%o;n=2b<^Qm(*jqeR(Qs>!TG5j&P^R~Uh0H%(i!-j&ehem_r9a{-{os(C4BHo zRS%vAc%}NjZY8{5RUKLTZ_nC)JLA7~)y=h!d97Ai80V`;@Lm)5IRPOnSSg2kZQ#)au{=vpON_{r|4c zuIl~&@2y{e&uI_-@fzMHeV*s7|2#}+TA%xG>ht|geV)Imx0m$_&ykvkmy6n4y~pb9 zV!idG_is|KYR|16^~(y^r}d;dmlLjD-+!+9%ap6P`P=>t&XwxB;5<=#ezyIa_qRGP z<9us%QkVMY`9<}A-oMlv`a9?GEpQ$jUrpq%UB|bq{W|&hHnm@;7~iV)>y+c$)_$F8 zeCvtF;rv;D_Q4stn#idQ|KWM75yX`|ZBFZa!l4u--e38a=Mxm|??K!LK96 z_QB_Iv*A0Aa36YaUAyXDoVnQC{@S78SCaXdhZBFDG-J`Kj_-=e;yK3J$ zd|bPGaHX!=tyInQ4WBM}&vy8zcDSc#_Y-bAey`qd!8J7haG2hAz|ej}*T;tqS#99p zp#yrY*%!3_y?XcU56Ymy{RR%kZ$sC=Z?8W62CRzWkiILg%_9CtLwohce+Lg-7ax!R z4jVFb{r-JHUb%1o{`C*gtoOk6`VPT6tUR!P|6YTJ^zDPs!O-;w^{tf#4DHo#K;OZ5 zw>A3>7|361Jg3hoX#m zgZr&KjMFi4)xiVT9)z~w&Ck*heD)`a=C@h=G>zDNr?I2P?S^!4-~PRZ_8T|=pT@05 z4BukZm@Q!*w07_Q{Z_^waomVq$HCUC4?Y9^h729tYv{nic+dK=L-66Fw;Qu1-wB`C zf$QRv)nnj*{_EpGzkdBzS*Ks$b^8uRm(iofY=hg!`gfS4N9cz4)AdiZ7|M&nPk z^4dd&4qUSbKZ*EA9K|E155*pT{rFK^j2JU+)W}gI#)7u_*ik!<9WfH0g>hT$wC!eN zhL0MJ5yQug8@~Bg+v5H3w>NHh?aw&4?<)P)!!xhf+CvAf+86d-L;LpW(Qo0U2K3lw z#BMv{b2obUZrklNZUG!{3-EI@V(bFb{O!Bf=$F^fo)MZPn~``{H-*Z8cBrBj&CBcfZ>EPKDp)(EV$*18V=B!#@6#73XyM zT$WE*ac-BNTlK_wVGDHO8L?$GU)_9l9dFtc|9@x2Nx#hO8sJJkPP3q0aEvi?y^Ji?Qw;ZZ9*;%Bw~_gdo-j=$;) z>>S|lWx{I6@pm$Te+RXJc-Jmf%LxnNahIyqgt=<<_7mz=c;MbzR2^y`$M)>GLW?P? zoA`&QR^oqZ_x@FN4t~20et$;xZTK@`6tgXHI^DAIZy$ezI^IR{WB+e|Ms@30fACCt zMvb4U@iCuKbF^)I%xBd2PK}RwjhcJk*`23**1ea_`vit3?F09F!cW4Gc1438sDk$9K>hXQ%%lU`?+%zpJ4~g z)cF3~?HJ#=@o`%$sK<9{e2l-Izd5Jzc`@mn*8U%y({NpN&p2Lm8qaBs=Q}>*8qaq; zr*S1$|DJ8|h-cOMM{V(l&z$;4?eK_d|J+>2KQ}Qj{oKUI{O`G0x}Khizpt)MuBwN2 zX*_4~f-ruH#>aeyzTD&-_@R5uXXu%o8{ePj!13K0AGg8P+n+Pr;|#UT)Q!L20auuZ z-7~l&Zs*=|M?fcB=@)gsKN+53AG*h#@f8jqy* zcr>;BUI*k@bvC?bM?Au!vf)uDJZiiGOom5|SAfp=sA=szyWkPO`tn6}4L(O$ap0n| zQo}Jve^>kgYsVbw|9HiLkKr-L_g@dc9@nXUB~r&v7A`(Ra6dN_(tWB{b*<{^@9OTx zXS%MMi2t^$?#A+n*Ql8BSD*jpd|SWr=ib@hX}kWv)Yf*>(4DJg zzSX`yQvGi?jr+7s_hcqi%a1i71b^R$h3g4~_-zhB$iaC;^EiqM%;^3JDz z|Ce`?({ydT^TYY}Z|?MXCZ2Ss*Wn-B>EX)<4R=?((`&q&<9=@A-5l@q8t>+Kr`LEl z$2&c)w;=q#M|ag$QyuUu?2f5^KW51r8j%{rA2WevmsC)OPLNW4<j?ca6`bYda{>=8M1zvg8qu~)h3*GoStlE3>Gu;iZT$uEz18Aw%lp- z@UcDTSa^{+dhu@$f76Bc8#H{&QDcVpm}5}Wm)qb4mw5ZMwj7z1b2+mA{IBdc|10$` zx0#fy<;bLune=PZ+qv=X_KKg|r+RbASicCyXS{BF$NE#uq?gZp!sm5u%YM=+h401M zN_hFqq*KbICCQ{350qBb65;)LNHneS$=|fbr+U*GpW;nxd}=qX@d@6v#;1AH8lUV< zYkbN#t??yEO>5YO`b!O)J%6cTOXV*$o`9S7n_SeU5Va|5wfa-&q|Y ztxYXz-9>F0QJWUENvG~fbL+oZBU+{x?>B>}%_wR!iQ3FfYCJ9RZN}O+fz>~%!|8qr z)cA-`wl1~T&$g*f!lSy{$)6uF>1}+qLG5%hzSq8!vYG0tKK#I5J*r+aH>5jNy<5y) zP1~X^@9?K>*RSecSM{vDSG(HBSL55EvOxU}|<&8K*8;zI}jBHslaA~AN}~}|f{`stM&7d58+jIy##UxzmN()`-Dt#=U}VdZv6UHl zR+C0t%Nzc#ndObRo;DgWB^cSVWNd9lJ~ySYjTxEcjre+BqY+brku6KcHfH2mUmDw* zky+k|uktk-F(nw;vSe&)MqY)av7H&2<&F5-UZW9Hf{`st#&%}pRZ$w-n~_=Gh_C21 z8Zjjp*|KD8Z$_TerLltY3yo7W_cs_!W)g45{ztFGIlj1SLM=(@3m<7JTl80@w&W5Bc=o+Tb7KIn~`@3 z(l~_~ndOc6`dFh8Q-YB#OU5b8$h#J4oYIWU@s;cPTwN-(lz$;fYrslOKTE>jw(HY2mV z5wDwVG-66HvSrCQwHbNWD~;XF$SiNfy<(#gQ-YB#OUCYIs(Wl6>z z5-FSBWX$q1yt<)LhABbDmL(bYX{2lhlQGN7u({YM!;~On%aV+{MN&4S$(ZG3_$p|l z3{!%PElV=)SxMPUCS#VD;VT%8GE50Fwk*lGb0%don~YgrhNg`&ObIf!Y);0#;vSGS z-AkNB)Ow2AtfDrXsLd{FbBNlUqBfVP%`Iy4h}yiOHlL`?FKP>j+Jd6Ckf<#zYKw^4 zqN28#s4XsPONiQ%qPCQ%EiG!xh}yEEww$OfFKR1@+KNqTyf*OpCy$ICc4RQi9~nLT zk%1}U$Y9Injtt6p6iC@DCS#VD&EjR45@c*ylJQuSvYsYmmY4PPGE50Fwk*kbMv$^u zO~x!Q!&iTSY1re(lptfvl8omiDVxn?%45@c*ylJR;aWpkN~ zSzd;(U^mJzCCJ#aB;!?6%H}p1v%G9>FT<1|W6P3^*KR4p&%|iBlV+Be&EsX55@c*y zl5qtgW%xNA4KikV8D3A)__v8ELB^IP8P_pVHlN9uMK8mYAY;qsWZb`939_d9x4lHI zx2UZwYJEhluc)meYO9LcYNFOp)K(X@HAJnysI4h#14M10s0|XewM1>Os0|Udp`y07 zsI4Pv>x$ZXqPD)MZ6InJirPk^wy~&fB5K2$)VNE^=bt<>Rs(Wl6?kQObIoj9FgR+siN|$k?(Z;~7E9RyG;4yliDJ z!;~On%aV-eB`ND;GG=*MA1}j{AY;ptjAutF>uWM*d0AgC!;~On%aV-eXenF8WX$ri zRlE#Sf{ZOoGF~a9Y*mvn%ga{vGE50Fwk*kby^^xkOvWrPTg}TbCCJ#aB;!?6%KDj% zSzgx9%P=L#*s>(!wOh(oHyN|MY;`Zglptfvl8h?|DO8W&OPjQ-X{wOERveq-;%-G0V%=^fF8dGPW$qxTce`0VZRXmksbTObIf!EXlaC zl(K;)W0sc<^fF8dGPW$qxW1LLK_+9CmkshVObIf!EXlaama?@>#w;&e%gZn&$k?(Z z<62+J2AhmoUN+dvFeS*?vLxdjhm;L58MC}>h?ikXkg;V+#`_*A8)`CUdD&1e!;~On z%aV+DUsATV$(ZG3YkL`{1Q}bFWV{EIvUN(!ov4(pYcghe*}7ha zDM7}TB^mFBrEEQuG0V%=^D;~cGPW$qcvmiE>zj;OUbeoMVM>s(Wl6@p0V&(SWX$ri z4ZI9ff{ZOoGVV}F*@h-#mX~elWtb9VY*~_VpG3+wG8wbHY$Gqjlptfvl8n1GQns~dl{w#8C#ZQ+%uB0O-#ltFWbb+FeS*?vLxfqm6Q!L8MC}>n3rKnkg;WRGVb4Q z3R%ICF%gcs)8KwjoTb5)z7Nu-6lQGN7HuEw}2{N`U$#_PP zvdvA#EHB&K%P=L#*s>(!c}dE)Fd4JFYzr^Llptfvl8k3ZDH~xjW_j5NFT<1|W6P3^ z=V&P#X)bGPW$qc8!y9@ zAY;ptjO!RF8*MUXdD&<$!;~On%aV+%DJk35WX$riZM_Urf{ZOoGOp>QY>dg6(!DqG5SFd4JF zYzHsHlptfvl8kG8DI04tW_j6IFT<1|W6P3^cN|i-qsf@%WjlHqrUV&VmSnu|k+N|n zW0seV^D;~cGPW$qc=sh`JDH4GUbd5$VM>s(Wl6?+Kq=eVWX$rioxKcGf{ZOoGTwT{bj|@x+M+RFqcVtk;qd>~`H5s$KY+o8W&3*>rUV&VmSj9HN!bA=W0sd4;ANN+WNcZI@$4vN2bzpo zUUs0DVM>s(Wl6?!w3HoWGG=+%L0*O_LB^IP8Lt#lcCg8qnB`@Mdl{w#8C#ZQT*pY+5hi1nmmT3{m=a`cS(0%zC1ppNj9FfGq?chzkg;V+ z#xW_j6hUWO?_#+D@+?>MCFc#|>9%Z~Rl zObIf!EXjD^BV{L;j9FfGf|p@Rkg;V+#=9>mJJDp!^0E`X3{!%PElV=q14`LRCS#VD zo#bVh5@c*ylJQPd%1$;Jv%KtNFT<1|W6P3^_rp?lipiMeWv6%WNcZI zapy|P&NdmdyzFc*!;~On%jRUT!jZw2%^ew(@hFh8b4|u9FFV)EFeS*? zvLxfNC}roFj9FfGo|j=tkg;V+#xsJHoo_N`dD;11hABbDmL(a_OHy`$$(ZG37kC+_ z1Q}bFWIQ`c*@Y%!mX}@VWtb9VY*~`=94%!RnT%Opc9EB1N|3Q-NyaOMlwE8xW_j7g zUWO?_#+D@+uUArbiOHDdWtVsvrUV&VmSntYO4+3*W0sd)>SdS`WNcZI@!BnAmzj)N zUUr$6VM>s(Wl6>rgp^%wGG=+%lrUV&VmSnsWm9m>n#w;(p*~>5`$k?(Z zqV|xeJuGUEh}xr~_L!(WE^1GR+LNO8l&C!|YR`z;v!eE#s68)gFNoTUqV|%gy)0_4 zh}x^7_L``@E^2Rx+MA;GmZ-hmq{dxRKL6yAagQAt%<@OZJ^skRlyGFQWphUcWjqR` z>|T>G%ggTdGE50Fwk*kbEK1pZCS#VD-REVP5@c*ylJSfnW%rwmSzdO(mtjhfv1Lid z^OBT3U@~TT*#lmNDM7}TB^l3-Qud(9nB`>;dKsn!8C#ZQJV#5}LndREmp$ZVm=a`c zS(5QeA!QGnj9Fgxu$N&WNcZIab+oGPn(Qc zUiP$?VM>s(Wl6^Mt&}}uGG=+%GhT)%LB^IP8CTg-_N>X6%$8KwjoTb5*8>r2^l zCS#VDJ?CYZ5@c*ylJSm1%APkFv%KtiFT<1|W6P3^_dQbfg2|ZWWiNOcrUV&VmSnv9 zlCl>~#w;&;(aSI;$k?(Z<2|61y<{?GdD%-|hABbDmL(bQM5XLylQGN7UiLCf2{N`U z$#_33Wv`fwSzh*vmtjhfv1LidyK*Ue)nv@_vRAzfQ-X{wOET^aNZD&9W0se_=4F@? zWNcZIafd?6UN;%DyzF%^!;~On%aV-yBvST<$(ZG3Z+ID|1Q}bFWZbQhvNuh}EH8W0 z%P=L#*s>(!o{^NjWin=Y*;`(QDM7}TB^h_Fr0i{zG0V%|_A*QfGPZ0^#{JuOAZxmR z`>v?HCu;AD+6SWcp{RW%Y9EW*C!+SLsC_1CpNrZTqV}bzeI;sNi`oQH`$p8h6}9g~ z?R!!CLDYT}wVy=oXHok_)P5DU-$ds(Wl6?0os@lLGG=+%S6+rG zLB^IP8CRB4_O;2FKZnW_j7qUWO?_#+D@+@5-g@7n3o|%YN}PObIf!EXlYx zAZ5Rrj9FgxtCwL)kg;V+#vKYN`^{v`^0ME&3{!%PElV=)lStX`CS#VD{qAL$5@c*y zl5w|2%Kk7Jv%KsNFT<1|W6P3^dqz_Br^%S*Wq*1ZrUV&VmSo(ylCp^=W0sdq^fF8d zGPZ0^#{Ju>?yvi|b)wcn)LM#KD^Y7LYHdWVt*EsVwf3UcLDV{mS|?GPOw>AyS{G64 zDr%F9+7zNTrKn9MYTZO_YEkPhYSW0?w4yehs7)_wGl<%ZqBfJL&D^BMT~a>(NeXOl6@%Q|}*rUV&VmSkL6N?8|^G0V%kcp0Vy8C#ZQT;EDrSCcWz z%es0QrUV&VmSkLIOWEWmW0sdq?q!$~WNcZIajh?9Q<#idUN(i7VM>s(Wl6?64k??` zWX$riDZLC+f{ZOoGT!${*;FQDmX}TCWtb9VY*~`=?n}zLnT%Op*3HW>CCJ#aB;!4x zlud0iW_j7vUWO?_#+D@+??k1nyUCd4Wf%-h!(APw1Q}bFWV|1ivT01lEH9hJ%P=L# z*s>(!UAdG^Ycghe*|c7UDM7}TB^mbyq-;8qG0V%Q^D;~cGPW$qxI-aj)0>Q0UN*g# zVM>s(Wl6?;5-FR(WX$ri8N3Wrf{ZOoGVaz$*^DM*mY2=wWtb9VY*~_V&q&H)WwV=%Szb1~mtjhfv1LidbF`GrVKQcU*&JSmDM7}T zB^j?2QZ}c_nB`@2dKsn!8C#ZQyk1G!Tn(~6CQh9APXhnKRfT^`s=~k8RN>!8s_-un zRmULkuj^F!cWf&Bi!l}cZI%lE3JSjrR^eavsPJ!SRQT5_D*U?;75)W=3jfwYg@09` z!q4xo&PJSrz)#<<@N=#!{6y&rKfAfYPf4yWLR^f%&)BW-(`+mJ+}H{~p|!%#Lap#q zM=Sh%&k8?Dv$_U>pAK2!=P*|IiH8+_wqS*y;#b{-z)z;D@H5~l{Ism0 zFi_!p`73bwfCgRAh0vkI^6s_-hR>PG}#8C2mlI2B&) zQsH$a6<)zn;k6PKUX@Vc>;Kh61iq$-FAG)`zD`!*D^eA{_EX`jEET?3Pt0mOocg%ArP7C|hESPZc^VhO~Oh@}uqBbGrdi&ze^JYogJiinjEy%4<-DAznwkfp`<~7UFHhJBW7??;+kte1P~6@e$%<#3zVP5uYJGM|^?!67dz{Ys3V^ zH;8W$-yyz7{DAlo@e|@_#4m_n5x*gRNBn{K6EP8iJFTh?(E`yD(F)NT(FV~L(GJlb z(E-sB(FrjbqBEikqAOx@#1x1r5mO<$A*M!jM@)m57BL-Sdc+Kf84)ufW=8Zt%!25N zm=!S_Vs^wFh&d5+A?8NRgP0dFA7XyQ0*D0>3n3OpEP_}Ru^3`;#1e=l5lbPKMl6F^ z7O@;+dBh5c6%i{TdLeovRz~ze^hK#1@DVh>?gb5nCZfA+|pNPDY%9I2Cak;&j9rh%*sq zA@fhN9#1n`o5lKy3gLoJ59^!q(2Z#?5A0a+Q ze1iBC@fqTC#21J!5nmy`Mod6_gZLKl9pZb$4~QQTKOufb{DSxu@f+fI#2<)15fc$r zOC0}*7KoOJR*2S!Hi))}c8K zi0Kg1BW6I%h?og6GolA#7DP|PtcckVvm@p}%!!x_F*jl!#Jq_45c4AzKrDz@2(d6? z5yYa1#Sn`lmOw0tSPHQ;Vj0A;h~*H=BUV7Hh*$~H3(*^~GNKQnFJcwMs)*GP{Sd1o z)F2zSPL;2F$6Ibu{L5I#JY&}5bGm0Kx~NE2(d9@6T~pYrikH)%@CU- zwm^(Pj6`gS*a|TUu{B~F#Aw8}h%t!m5ZfbmK#WD~h!}_139&O`7sRfJ-4MGY_CV~3 z*bA{YVjskK#J-6A5c?wzKpcoT2yrmt5X7N~!w`ofjzAoVI0|tz;uyrSh~p5)BThh^ zh&TyxGU61(sfg1Mrz6floQXIKaW>){#JPy`5a%NUdK z3Gp-H7sRiK-w?ke{y_YRn24xa;rK_iK(s`(LbOJ-L9|7*L$pV9Ky*ZOLQIC}jOc>s zikKWR1!79XRETbfsS(`~(;%irOox~rF#}>o#7u~p5j_yIAbKKZMa+hn9We)DPQ+Y@ zxe@aq=0(hhm>;nKVnM`0h=mc0AQnX|hFBc21Y$|VQi!Dy%OI9TEQeSgu>xX6#7c-> zh~9{m5q%JS5vw3pMXZMChgco42BJS=O~e4iK*S)#T8P1jA&8-fwGrze)a1OAci3}MGQx5hS(gj1!4qZBw|a%R)|rEtr6QGMkBUGj6rON*dDP1Vk}}u z#5lxGh@BC;Aa+IUhS(jk2VzgeUWmOB`yj?6_C@T6*dK8K;y}bfh=UP_APz+whBzE? z1mZ}9dQQYOvG7;vk~VY&PAMuI3IBV z;zGnlh>H=IATC8*hPWJY1>#D?Rfww**C4J%T!*+GaRcH;#7&5s5w{?2McjtC9dQTZ zPQ+b^yAk&w?nT^(xF7KV;z7hih=&o6ARa|LhIkzD1ma1=Q;4S#&mf*fJcoE5@dDyS z#7l^m5w9R#MZAW19q|U@O~hM>w-N6k-bK8Jcpvcr;zPtoh>sDUAU;KWhWH%u1>#G@ zSBS3>6A<4ZzD0b8_#W{C;zz_!h@TO^Abv&shWH)v2jWk}L`2mZ$3LP4q9vjgqBWuo zqAj8wqCKJmq9dXcVlqT$L>EL?#N>!65K|(iLUcn+jp&Y;1~DySI>hvd84xofW!J@EE24y*2p^)M1z9-|S4zRp-72iCxC9Gs%|S>#CmJTT~nM?A>zLsvWYP^R=6{ zT}v#s>MDLcgI_h98P7rgHlAB0b2scv)@L)-)wX4-UOlQ_GdH9=;f`%~+*!7*y5kAf zt{*p>diLQBEngfzx0hi`kg<75#;@H;**qp=mY2=rWtb9VY*~`=D~M7yugRF@W%GI& zrUV&VmSp@orj*TRGG=+%d|rkrLB^IP8NZq;W%HYiSzb23mtjhfv1LidujxwJ0w!aY zmo4CBm=a`cS(5Q9%Tl(W$(ZG33wjx*1Q}bFWc>QJlr3a3W_j5{UWO?_#+D@+zsfFU z3!98tUbe88VM>s(Wl6@b^-I|zCS#VDE#hUE5@c*ylJOM>QnskcnB`@QdKsn!8C#ZQ zeBFbTEoL%idD&uKhABbDmL(ZqeIaFwn~Ygrwz!vJN|3Q-NygWJNZAr5W0sdK;boW- zWNcZI@s%i2wxr3JWy_n4Szflhmtjhfv1Lid*NjQo3MONgm#yGsm=a`cS(5RU zYf`qN$(ZG3D|#8G1Q}a4C*$ikSAwkR>o zRWHMoAY;ptjMpnETg_z5^0L*u3{!%PElV<9HKnYd$(ZG3{k#lQf{ZOoGG4o-Y;}_{ z%ga{xGE50Fwk*lGf{?N`OvWrPTf@sRCCJ#aB;z_p%KDp(SzgxP%P=L#*s>(!YD&u1 zG#RtJY)vo2lptfvl8kFQDH~uiW_j5FFT<1|W6P3^D@!RGXfkGb*+4JDlptfvl8ozH zDH~)mW_j5lFT<1|W6P3^t86J-%Vf;*vbDSnQ-X{wOERwYrEIXtnB`@Iy$n-=j4ewt z-f>9T5R);>%Z7LvrUV&VmSnu|k+PvCW0sc<^)gHeGPW$qc=sh`YnzN&UbeQEVM>s( zWl6?+Kq*_tWX$rib-WByf{ZOoGTw(!ZjF>}Y%*qf*~VUmDM7}TB^mdO zq-+zDG0V#~@iI&aGPW$qxN{|C!%W63FB|4%m=a`c*_@2~x0^!NbpLj^sBI={n~T~O zqBcU*MvB^&qPCT&jS{u3MQs~V8!c+virN@a+fLNB7quNkZLFy6C~D(GZ6{INS=4qB zwOvJRH&NSN)b(!*-^?yn2cFoHp0s=CCJ#aB;z?+%0`-uSzb2M%P=L# z*s>(!l|st4G#RtJY)dc0lptfvl8o0YDcj0q%<{6WybM!1tGG=+%7%#(=AY;ptj4Mki+s4N|lQGN7w)Zkj2{N`U$+*gvvK>suEHB%^%P=L#*s>(!T3^b> znv7XqHrC59CCJ#aB;y^2l(!UAdI)W-?}Z*=}BjDM7}TB^mbyq-=MSG0V$#_cBZg zGPW$qxI-ajdzg$_UbcsqVM>s(Wl6?;5-Ho$WX$riJ-rN5f{ZOoGVaz$*7sUqsGTWlXNlU`O={dF(!u_$HxnT%Opwx5?_N|3Q-NyamRl z2bqjnUUra|VM>s(Wl6>>g_Ip^GG=+%!Cr8KwjoTb5+J zYD(FmCS#VD9qMJ65@c*ylJVLtWrvxJSzdOSmtjhfv1Lid6@-)>ZZc+h+2LM>DM7}T zB^lQ-Qg(#NnB`?hcp0Vy8C#ZQTun*YktSo7mmTS4m=a`cS(0&0CuK*Oj9FfGl$T*j zkg;V+#+9X%9c?mZdD+okhABbDmL(b2w^DYD$(ZG3$9Ng01Q}bFWL#xS*|8>LmX{st zWtb9VY*~_VtuJNAnT%OpcAS@CN|3Q-Nya-4DLdX|%<{71y$n-=j4ewt-uFn^2_|Ef zm!05cm=a`cS(5SYOUh0(8MD0XL@&dXAY;ptjQ4<2c9O}MqIQ|6T`p=@h}xB+c9p1I zEo#?@+O?u~ov2+eYBz}5jiPpwsNF1Tw}{%UqIR38-7ac(h}xZ^c9*E#-K54{Qa=CW zk#UY48O-uW#yS4Tz?5)guw`>c24y@7r0iUiG0V%&^)gHeGPW$qcq~fUc_w3)m!0Qj zm=a`cS(5RLAZ6#9j9FfGzL#N2kg;V+#`BVtU0^b1dD#VChABbDmL(a_j#74^$(ZG3 z7kU|{1Q}bFWIRVp*+nK}mX}@RWtb9VY*~`=N+D$zn~YgrcCnXXN|3Q-Nyh7ylwD#n zW_j5qUWO?_#+D@+ubNVJsmYk-WtVyxrUV&VmSntkOW9>6W0sd)=4F@?WNcZIaRnh| zmz#`PUUs>cVM>s(Wl6?$jFeqrGG=+%6<&rZLB^IP8CO$McBRRfQtj9FfGi056 zsJ$g>Z#SuNmz2*xd1TyUM+UR}k#UbdGB70^8Eo0ykwF=c0x7%KWX$rid%X-(f{ZOo zG9HUkcAv?Z;c^Rez8C#ZQyi!Qn!zN>vmp$xd zm=a`cS(5R3C1sD8j9Fgxh?ikXkg;V+#;c~3J!&#$dD)|0hABbDmL(al-BR|L$(ZG3 zk9irU1Q}bFWL!Z=+2bZ-mX|&5Wtb9VY*~_V9V2B=n2cFo_Jo&VN|3Q-NygQbls#!O zW_j6@UWO?_#+D@+*K|_$l*yRoWlwn-rUV&VmSkL6O4-vUW0sda?PZt}WNcZIaeXUg z&zOu^UiOTaVM>s(Wl6?Wwv;_%#8KwjoTb5+J z_8rKY?%%#EYVV2K`=a)NsC_7E zABoz>qV|cXeJX07iQ4C)_Jyc@DQaJd+Sj5sLDaqxwQoi3J5l>y)P4}PA4TmaQTtic zei5}_MeR3H`(4!j5Vb!=ZDNxecS-sDlSjroc4RQi9~tlXBLh>yk-?VD9T}AID3G#u zO~x!Qd)Lb_CCJ#aB;&CtW$&4cSzh*@mtjhfv1LidGlG=8Z!%_i+529GDM7}TB^l34 zQucw#nB`?3cp0Vy8C#ZQJUdF+hbCi|mwo7Em=a`cS(5P_EoC2>j9Fgxk(Xggkg;V+ z#w&%CeQYvjdD+KahABbDmL(alS5o$g$(ZG3pLiLj1Q}bFWV~uh*{3FBmY03%Wtb9V zY*~`=+AU?DnT%Op_L-MqN|3Q-NyZg~lznb8W_j7?UWO?_#+D@+*D+G|g~^!ZWnXw1 zrUV&VmSkK_N!gbsW0seF>1CJ_WNcZIaZM*>Uzvs(Wl6@BrIdYbGG=+% z*ItGxLB^IP8P~T`Ho;`f^0EnDhABbDmL(Zi*;4k6$(ZG3-*_3O1Q}bFWL)b@*|#QR zmY03&Wtb9VY*~`=jzh}6Ga0kI>^m>Rlptfvl8pB~Que*cnB`^Pdl{w#8C#ZQy!(=} zA56w9FZ;pEFeS*?vLxd@pp^Y+GG=+%k6wl;LB^IP8Sg};>?e~k%gcW9GE50Fwk*kb zKP+WGn~Ygr_Oq8^N|3Q-NyfW!Df`7_%<{5dybM!Hn8KwjoTb5+pt&y@nOvWrP`@_pH zCCJ#aB;%ful>KQkW_j74UWO?_#+D@+cdn#tqRE)$WfQ#&Q-X{wo0D<>wrcU${o6WG zYawbaMXi;nwHCEDqSjW_+KF0wQR^UT9Yw8^s7)qnokgvSsC5;!$wh4nQJYfKrV_Po zqBgatbr-d1L~UA8n@-fG7quBgZAMX>Nz`U;QsXWupMUbms9Llb-)rA$gF3v+zd17S zRS6sn4gaNeXOl6@ z%Q|}*rUV&VmSkL6N?8|^G0V%kcp0Vy8C#ZQT;EDrSCcWz%es0QrUV&VmSkLIOWEWm zW0sdq?q!$~WNcZIajh?9Q<#idUN(i7VM>s(Wl6?64k??`WX$riDZLC+f{ZOoGT!${ z*;FQDmX}TCWtb9VY*~`=?n}zLnT%Op*3HW>CCJ#aB;!4xlud0iW_j7vUWO?_#+D@+ z??k1nyUCd4W!=3DQ-X{wOETUMOW8CgW0sdq<7JoUvgu96EH9hh%P=L#*s>(!K8cjgU@~TT*$iHW zDM7}TB^h^Xq-;i$G0V$l^fF8dGPW$qxMw6~GntH8UN)1LVM>s(Wl6@JD=C}VWX$ri znY|2Cf{ZPjlX3sH2V_lOe>;n)^%S*PMQt`wn_blA5Vbi)Z7xxpTh!(ewRuHtK2e)r z)D{r61x0NkQCnEl77?{YMQt%rTU^wZ5Va*mZ7ES(TGW;iwPi(ZIZ<0))K(C+6`Rz! zOUmb;JTiLNk-;o~Wc2Vy2Bw4~gDsmoGAQFwAZ4?dj9Febi+dYX(` zUe?pgFeS*?vLxdfLCR(|8MC}>RxiVpAY;ptjOQgOo6Tg*^0L{y3{!%PElV<<9i?n` zlQGN7X7@5o2{N`U$#{;IvN=q~EH9hG%P=L#*s>(!l|stqG#RtJY)&u3lptfvl8o0Y zDVxh=%<{6iybM!su*X$Yjj&vW2`1 zQ-X{wOERvqrEFo7G0V#q_A*QfGPW$qxYn1lMNGylFI&XRFeS*?vLxdjhmDKEp6AY;ptjCbWy zwzSEZHV%a-#pObIf!EXlZABW25*j9Ff`yq950kg;V+#yuk`Tft<^^0F1Y3{!%P zElV=)TuIrACS#VDt>|T#5@c-IoQ(UoD?!$D|F)N?^%k|2MXish^%b>ML~T`3TTRsZ ziQ4L-wuY$n7qvA-ZGfl^6tzL3ww9<37PTRwHdNHs7PWOmZCz1YPt?{IwGBjVLs8pE z)HW8iO+;;2lNxtP`TUbd#!7Z%Fv}kqEBPY>Q^JwKmdza*l<_E#vR)=*mY4PNGE50F zwk*kbEJ|5#lQGN7dV3kB1Q}bFWIQ8C*~%tkmY1#UWtb9VY*~`=yd-6POvWrP>*HmZ z5@c*ylJV>)WqnP?EHCTpWtb9VY*~`=94%$5n2cFowu+ZwN|3Q-NyaOMl&xwqW_j7F zUWO?_#+D@+uUAsGn#q{uWvh7^rUV&VmSntYN?AXXG0V&Pc^Rez8C#ZQymm|3>Lz2B zm#yw)m=a`cS(0%DA!Tcrj9Ff`hL>SVkg;V+#&wL8^*0%_ysW>MVM>s(Wl6@>l$5P$ zGG=+%nqGz}LB^IP8P{}DHo#=e^0EP5hABbDmL(ZimQps*WX$rifnJ6wLB^IP8P~T` zHppbm^0Gl*hABbDmL(Zi*;2Na$(ZG3Yk3)_1Q}bFWL)b@*He2{N`U$#~x*WkXHIEH4}CWtb9VY*~`=?n}znHW{V=u#$AY;ptjC)2>wu#A@ z+^%HWRhYMQsaF8zE{V zMQuw_+e*|%iQ3kpwvDKb7PW0fZH%aGCu-Y^+76;NR@8PBwQ-`hlc?=1YP*QquA;V^ zsO>Ildx+YeqPCZ)?Ja8iG^ufyl+QnTWNd0j2DAK;v8g{YFeMxrY}wqAK^cz%DI0Dw zW_j6gFT<1|W6P3^$D)*NW-?}Z*=AmbDM7}TB^l2MQntCtnB`@gdl{w#8C#ZQJTFPv z7A9ksmu=x?m=a`cS(5SWC}kr|#w;%z;boW-WNcZI@fBTdFEFB|D)m=a`cS(5Qe zA!S>dj9Ff`rI%q!kg;V+#_N@oZDlfMdD&K8hABbDmL(alno>5(WX$riQC@~ALB^IP z8L!<^wzbKa-+nv7Xqwyl?8N|3Q-Nyasul#MYNv%GAKmtjhfv1Lidm8FzzXEJ7a*>+xr zDM7}TB^lSZQntOxnB`^Ldl{w#8C#ZQTxCny4klxkm+jzXm=a`cS(0(BFJ)s*#w;%z z>t&b{WNcZI@s2~vb~G8Yylh7=!;~On%aV-uJyJH#WX$riabAWgLB^IP8SlQNY$ua3 z%gc81GE50Fwk*kb4=80jn~YgrwzHREN|3Q-Nya--Dci+l%<{5bybM!H!s7KAY;ptjC%u8w!6ug5j8KwjoTb5+p zp^&mYOvWrP+r!H+CCJ#aB;!7blj9Ff`x0hi`kg;V+#+@rE+s9_C$-%gYY*GE50Fwk*kbj+U~6OvWrP zJIKp0CCJ#aB;%Dr$__Rev%KtJFT<1|W6P3^*DEPI#AM9!vO~NKQ-X{wOEO+HrR-3X zG0V#i^)gHeGPW$qc(! z%2LXXHW{}W5;lptfvl8ozHDLckw%<{5hybM!}8k|WNcZI@qSp!PB9s?yzCS&!;~On%aV+D(!4uzDRZZc+h+38+}DM7}TB^mcgr0finG0V%&@G?vZGPW$qxLYG- zXPS&zUUsIJVM>s(Wl6?8BPlz}WX$riv%Cybf{ZOoGVWYS+1VyzmY1FFWtb9VY}uTQ z`?u#n)^z{&Tv0nu)Xo>R3qhyG+zB7qu%y?MhL*O4P0vwQEG} zT2Z@B)UFq`8$|6!QM*ahZWgs$MD12lyG_(?7qvS??M_j(!c}dDHFd4JF>;f;tlptfvl8k3ZDZ9{Q%<{4ey$n-= zj4ewto};DgB9k%8%P#UVObIf!EXjDKkg|(S#w;(p*vl{_$k?(Z@t%v%gZkFGE50Fwk*lGf{?PyO~x!Q zyWGn#CCJ#aB;z_p%C0aOv%KsIFT<1|W6P3^t0^hF(qzo?vMap|Q-X{wOERwMr0gn_ zG0V%Y@-j>bGPW$qxU!V8t4+o%FT2{yFeS*?vLxgBR?4n18MD0X8ZX0?AY;ptjH_%Z zyVhjP^0I5a3{!%PElV=4^`-1OlQGN7uJbZX2{N`U$#};hW!IaGSzdO%mtjhfv1Lid z`yMH~!DP(xvKzb%Q-X{wOETVlN!g7iW0sfQ=w+A^WNcZI@g7jhZZa9OyzC|~!;~On z%aV+DqEdFV$(ZG3H+vbT1Q}bFWV|1ivRh2XEHAso%P=L#*s>(!UAdIqYBFYd*{xoN zDM7}TB^mbyr0h16G0V$t^D;~cGPW$qxI-ajx0{SvUUs{eVM>s(Wl6?;5-GdGWX$ri zJG=~2f{ZOoGVaz$*_|e1mY3b>Wtb9VY*~_V&q&JdG8wbH>@F|Elptfvl8if7Qg*k= znB`@6dl{w#8Cy0d?FCVLQPf@%wUgIWH_xW^wEm=cZ*wruXmpo~X>l-+AGW_j7YUWO?_#+D@+k3}iF z&t%N(!Ra43yH5s$K>`^bnlptfvl8o1GDSOOh%<{6wybM!< zj4ewtt{|lBag#C2%O3YKObIf!EXlZzk+LUD#w;&;!pkrv$k?(Z<7!IEo-`S=yzEIY z!;~On%aV+1Iw^a~WX$rir@RbPf{ZOoGOjG8>}iuR%gdhjGE50Fwk*lGzLm0POvWrP zd&bK!CCJ#aB;zVu%APeDv%KtCFT<1|W6P3^Yketu&ScE;vgf=EQ-X{wOETVZNZIoy zW0sda?`4=0WNcZI@xDjOUN9N6yzB)p!;~On%aV+DUsCp>$(ZG3FM1iK1Q}bFWV{EI zvX@N8EH8V>%P=L#*s>(!ov4((Y%*qf*~?yrDM7}TB^mFBrR)`xG0V$d@iI&aGPW$q zcvmiEubPZmUiPY&VM>s(Wl6@p0V#XUWX$ri*SriE4Le#z#wXa0&Yf+mZYTt<3x1#o)sC_SLKZx3oqV|)h{VZy~h}y5B z_M52vE^2>>+Ml8}u}O`)qt&b{WNcZI@mQ3y_e{ntFMH3+FeS*?vLxdfLCW4Y8MD0XeJ{h5AY;ptjOQgO`@m$( z^0E)S3{!%PElV<<9i{9;lQGN7KJ+q72{N`U$#{;IvX4y0EHC@W%P=L#*s>(!l|srs zHW{|-y(lptfvl8o0YDf`4^%<{5NybM!f{ZOo zGOnhi>`Rj|%ges>GE50Fwk*lGrjxR-OvWrP`^w8OCCJ#aB;(3b%Dy%kv%KtUFT<1| zW6P3^>su+CU@~TT*#s}clptfvl8mctDf`A`%<{5tybM!C^0M!}3{!%PElV=qeM#95CS#VD z{orMo5@c*ylJOo;%6>E%v%Ks_FT<1|W6P3^ccN1ElgXInWj}ctrUV&VmSnsima?Bs z#w;)U*~>5`$k?(Z<6XIw{bDj^dD$;shABbDmL(bY2Bhp)lQGN7e)Td;2{N`U$+$xy zWxtt>Szh*=mtjhfv1LideG)1A-DJ%2vfsT7Q-X{wOET`(NZB7IW0sfw;boW-WNcZI zanDG~{xli0yzEae!;~On%aV*cS5h|7WX$riiC%^&LB^KN$+&-8wfyV;ZJnsK5Ve+~ z)=Jb`i&`5|Yb$E)M6JE3br7|VqSi^&CKI*JqSi&!x{BK5qBe!7O(|+qiCQ;Nn_ASm zi`q1zHm#^lCu-A++61CJ_WNcZI@w_ButxU!&FKgvxm=a`cS(5SWC}pip#w;&u?PZt}WNcZI z@fZA`{2FKgpvm=a`cS(5QeA!Th%#w;&u>t&b{WNcZI@p>g??M%ijFKg#zm=a`c zS(5RpDP`?V#w;&u?`4=0WNcZI@!BnA9Zbe7FYDlCm=a`cS(0%DA!Qv+#w;)E=w+A^ zWNcZIaUCONolM3oFYDxGm=a`cS(0%zC1sPDj9FebnU`Tokg;V+#xs(Wl6@BrId9s8MC~sis(Wl6?WwvGG=+%G+u@&LB^IP8Sl!aY+92s%gd(qGE50Fwk*lG zHy~xxnT%OpHl3GYN|3Q-NyZ%tDVyG8%<{77y$n-=j4ewt?vqH_3?^fim(Ad1m=a`c zS(0(LM#^S18MC}>MlZvZAY;ptjC)2>Hj~Mi-dxo9Jd2-5jEuQ*?8QZf?=dBf5D-H=pR{7u^D)TTpa;MYoXX z78cziqFYpS{X)knDS!Utozc_o3{w5h=;`l_<|?f_gUQbB3}oB|QZ|jrNOjpXUe;Wt zC1Wy^aW6_)FO!k#vR+=+T%{#rGL!K{kg{n_MykuE^|IzFEg6%UjOUV+O=mJvT{fMU zHCJiLn9O869i?n~lacDO>AkGEN=wFMCgT|`WxY*Cs>^zNS#y<^jLA&KFNKuNU@}r& zHiMTnS82(Z%w+stN!g4hBh_UydRcRomW;_v#;>N7&15oCT{e@KHCJiLn9OASc1zjJ zCL`5lGkaNcm6nXjOvW38l+9u?Qe8HSmo-;u$(YP!yvImcACr;lvOZqcT%{#rGL!K( zC1takj8vD+>SfJUS~4ax8SivbHk-*vb=hoQ)?B3}V=|NRW+`Q}n~YSK&F*E*Ra!D8 zGa2uT%{#rGL!N3C1vxPj8vD+ z>t)SVS~4ax8Q*|XHlN8zb=iDg)?B3}V=|NRB`Rg}n~YSK&F^K+Ra!D8Ga28*QnrA} zNOjo)Ue;WtC1Wy^@l`Hm3!02nmo4aJ%~e`5CNmjl15(!4WTd*Rua`AfX~~$(WSme) z*+M2G)nyBLS#y<^jLA&KIf;}lY%)?^wy>8qS82(Z%w(L_NZBGLBh_V#cv*9mmW;_v z#+i|nEow4SUACx~HCJiLn9O9HTuE6!lacDOeqPpGr6pssGa2W%i#4)%e%oJki;Hdv z(Jd*ur9`*1=#~-PvZ7l~bjyow1<|c2x|Kw?vglS3-2l<8D!SD~x4P)o5Z#)hTT66n zi*6m!tt+~LqFYaN>x*s!(QPQYjY7vMDS!Utow1nR8KnB1v6#Oznya+#3?@6fGmvo? zNLhcAk?OMkUe;WtC1Wy^aW6{Q;wB^2Ws7@RbCs5i$xOx*LCTge8L2K?!poYgv}8WZ$)n&_h zS#y<^jLA&KZ?}{!Z!%I{w!D`$S82(Z%w)VlNZAS|Bh_Uqcv*9mmW;_v#(RvEt!Oe* zUACf^HCJiLn9O9nO-b2GCL`5lD|uOSm6nXjOvXE%l&x$sQeC#Pmo-;u$(YP!yje=w zDkdY$ITRa!D8GZ|kTQntFuNOjrjUe;WtC1Wy^@!cb3YnY5wm#yJt%~e`5 zCNmjdUsATF$w+nCnqJmir6prBlkp8GWowy?RF|#gWzAJuGA1(_U!qdBw#i6!+1g&# zT%{#rGL!K=EM@DMj8vDc<7LfNS~4ax8DHg6wyw!Yb=kUJ)?B3}V=|L*HXvmKO-8EA z26|a@m6nXjOvVX?l&xnnQeC#5mo-;u$(YP!oRdh|`X(dQW$SxcbCs5i$xOy+jg)O* zGE!Z(ftNK`X~~$(WSkjE*@h+~)nyxcS#y<^jLA&K$(59CWHM4+wvm@LS82(Z>`cb_ z?Z%BPp5G1<-6o>jRCJq(Zm{S!7u^uiZ6UfXMYomcwiewsqT5z<+lg*_(d{6*9Ywd3 z=yn#}E~492bi0XechT)3x;;g=m+1Bu-9Dn*S9JS{ZvW76O3I%JA=v2?hIty1yVN1WTd)mke4-AX~~$(WZa8Vwu#9|b=f9f)?B3}V=|NRM3Ayg zO-8EAHubXRDlHk4nT+R>lx=1*QeC#0mo-;u$(YP!JRPNMu*pbu*ej8vCx?PbkX zS~4ax8SgPtwvEY1b=fvv)?B3}V=|NRHYH`-nv7JJZR=&tRa!D8Ga2u6QnsDRNOjqE zUe;WtC1Wy^@n$Jy+nbD3mu>H5%~e`5CNmlDw^Fu)$w+nC4qnz=r6prBlkt`8T%{#rGL!MeA!R$8j8vEH>}AbWS~4ax8Q(op zwu{L~b=fXn)?B3}V=|NR^(AGynv7JJ?doOCRa!D8Ga282Qns7PNOjq6Ue;WtC1Wy^ z@g*u{yPJ$um+kIl%~e`5CNmk|!&0_~$w+nC9$wa5r6prBlkrt9WqX>8RG01PWzAJu zGA1(_X9H5Um&r(V*&Jx|(qB|#aoRaeA zPu>|r?am<8?~I}T&SOT_9z{Oh&59hIv_Ym6nXjOvb$^Wy4KIs>_CZ zS#y<^jLA&K6G6%jFd3;XJHX4DtF&ZHW-^{jQg)!pNOjqPUe;WtC1Wy^@pP23gG@%M z%MS9g<|-{2lbMWXw3HoeGE!Z3u$MJgX~~$(Wc*S{*&!w))n$ixS#y<^jLA&K@0FAt zYBEw?cBq#%S82(Z%w+s(O4(s1Bh_Vxd0BInmW;_v#&5Tj9d0sGU3R#aHCJiLn9O9n zK}gvVCL`5lM|fFtm6nXjOvZbRlpSd@QeAeWmo-;u$(YP!yiG~jQ6?kRWk-2gbCs5i z$xOyOos=DIGE!Z3w3jtkX~~$(WV~5Q*)b*~)n&(cS#y<^jLA&K`>m86Ycf(@cC42* zS82(Z%w)V}OWAQIBh_Wcd0BInmW;_v#=E|h9d9yHU3R>eHCJiLn9O8+aY)$-CL`5l zCwN(Nm6nXjOvZPQl$~fYQeAeUmo-;u$(YP!e0@pTNhTxJWhZ%AbCs5i$xOyKpp>0# zGE!Z3vX?biX~~$(WPFKA*(oL?)n%u6S#y<^jLA&K_pp?mYBEw?cB+>(S82(Z%w&9( zOWA2ABh_W6d0BInmW;_v#@T?Boo+HxU3R*cHCJiLn9O9HP)OMsCL`5lXLwn2m6nXj zOvX8hl$~iZQeAeYmo-;u$(YP!oYqL$StcXZWoLO=bCs5i$xOzXk(8ZnGE!Z3wwEJCAzCcca7+-72S2ByIyoRi0($w-6Xo3MR$wnZWY~aqPtymcZlvz z(cLAwyG3_T=r|?i&!4;>dmDZiXWM_8_RVV zuF{e*naOxMO4&swBh_UWd0BInmW;_v#xq*VE;bpdF1y&vnya*AOlC5EDWvQYlacDO zOT4VPN=wFMCgb-?$}Tk-sV=+J%bKgSWK3oD)v}8ar`mthq`{#$+brJx0o|G#RNbyVA>=tF&ZHW-{KU zr0gn_k?OLmysWuOOU7g-;{vO>arWWthq`{#$+bryGP1yG#RNbyV1*k?OLW zysWuOOU7g-;~P-QZZ;XIF1y*wnya*AOlC5^M5XK&lacDOTfD5fN=wFMCgXcp%5F6o zsV=+K%bKgSWK3o<*KW>ash$thq`{#$+broJ7j*G#RNbyVJ{>tF&ZHW-?A|r0gz}k?OL$ysWuOOU7g- zynya*AOlC4puB7Z9lacDOd%Ucr9{PsT4-7mTa zME9WR9unQdqI*PikBaUw(LFA@Cq(z8=$;bY)1rGubkBls#xNQeF0-mo-;u$(YP!JRPO%A(N5nvWL8^xk^jMWG3SoEoBdzj8vCB>}AbW zS~4ax8NU=#_K3+yb=f0c)?B3}V=|NRdnIL$nv7JJJ?dr6Ra!D8Ga0{{Qudh1NOjp` zUe;WtC1Wy^@!KtBkDH8Cmp$%f%~e`5CNmjt5K{Jp$w+nC6JFL_r6prBlkpxSWlx%n zRF^&JWzAJuGA1(_Z&OnCl*veS*;8KDT%{#rGL!L6CuL8Yj8vCB?PbkXS~4ax8E=+S z_Ke9$b=fms)?B3}V=|NRek*0qnv7JJJ?mx7Ra!D8GZ}B$Qudt5NOjqBUe;WtC1Wy^ z@vbjr&zp=?mp$)g%~e`5CNmjd98&gz$w+nC3trY-r6prBlkwdnWiOhHRF}QzWzAJu zGA1(_Utd!8lF3MQ*-Kv5T%{#rGL!KQC}l63j8vDs>}AbWS~4ax8DFAO_KL|!b=fOk z)?B3}V=|NRJuGFfnv7JJz3OGnRa!D8GZ|myQudn3NOjq3Ue;WtC1Wy^aW)`jubYfi zm%Z*~%~e`5CNmi)6jJtv$w+nC8(!92r6prBlW|TWWpA2{RF}Q!WzAJuGA1(_r!`Xc zmdQwU*;`)LT%{#rGLvy;BxP@#j8vDs?PbkXS~4ax87Eg#_KwL&b=f;!)?B3}W3n?D z=eO@RvUq;`p6K2e-3OxkP;?)O?qkt?5;{)N@X5b&@7iBEQvI*oyZ*0SbCuR#IVL;% zD~F7~N>cWo$w+nCdtTODr6prBlkq<*DSO{!q`K^VFKe#Sk};Xd_!A>#ADE0(mwn)6 z%~e`5CNmj-Zl&x)lacDO5526pN=wFMCgbjrvX4wgs>?p|vgRr+8IzfedsxaoHW{fd z``F8xtF&ZHW-^{6Quc|-NOjpKUe;WtC1bKP*_RyyI>zqk`t7nE9UbR(^!T<%^Iy9( z|JgBPN7Hxvc8(^F-O>Hq=I=KBsNasmKNkL@YsYB+{_FVHjMV&Ni*_Ba}&cw8?;e8~xi4ZJPi6kLkO1 zCw3Xu{3G{upZVW^#Vr5+?eKs9xf66u*D*~;NAD$i_gZ7iZ3pk%bLGLi_groJZ3k`F zXSKmYcHL&sPQ7~1Ialxg6L!qPf1AlaZ@{1-TW&X~XYTU$$-AJMvS#+a_u7~JG72Rl}8(nn#HH!azj48UYL^rnR z#u44Pq8m?i42i9|QC=q3@}q@tTlbd!s23eim|x~W7rb?E;3_g3#{9uM<2 zf8zf0_qudU)v>^T{~mu1$L?6L`CDFe__Hk@ru@O|KD7U^j)DJ4)rTqns*dLQ*`;H) z9{)~sa}U%1Om_A#MaDl%%DS42RF^e3=?GcZj<$@+OvW85W!+3hs>_;Z$Ou`tAY(F< z@tlyd?j|GEWz92xgsgjzF`3DDT1nYRCL`5l&F|z0*+@agWG3U8DP<#@j8vC3zpf)> zBL^9inT#j5l#OCCQeD>k_>Yi{5@bwfGJZd#tcS@+by@SCF+$cO$e7Gz{EA80s3s%T zWuto8s6obLCgZnM%0@F8sV-~Y>_+@G8!gC~%w+tcOWEiqBh_W2d)eqg#$+bry+X>y zFd3;XYhLn3{52aR$e7GzyxmCIm?k6DWn+5Tm_f#5CgUAT%EmGosV-|?bVvL(8!O0| z%w)XjNm=t<^xt<4QeD=(ijR;rA4SdI>E6++Z_i%5doY>Fc;A$=aZE<4%bHJt5wdZD zjLA&KTd$OjYcf(@Hm;YA8)QsoGTyzVY&?^Z>ay{?Y`h?2GL!KIAZ6p5j8vD6?`7i$ z8Izfe?+_`Qz+|MlYyvNvAjp`^WPA-t*@Pw|)nyZU*@QvHWG3UAOv)xQ8L2Lt$jc@Q zGA1(_UxrdPvB^kv*~DHpagZ^Y$@o5%vPn!vs>>$vvPpuB$xOyqvXo6~GE!YOsh3R} zWK3o~r`-SndCExH**H>2og65Y(An?-beL^rGGW)t1) zqMJi>bBb;*(akNoc|* zok6PK89n`-(KFl`Om=o>Amc8OvT004s>`PFvT1^h$xOz*C}q7&Mykttd0DR@V=|NR zM3Ay+O-8EAruDLEgN(^c#&b!^rZX9-E}PEFrVBDAGZ{}uDVyG8q`GW+FPlEdn9O86 zqou63$w+lsZ!hZ|WK3otI0@p*{ohRYmhOS$#|!eve`^Vs>^2cve|-+ z$xOzZrIgKXGE!YOyO+%#WK3o<-fyLB4wI4UvN^nLjv!+)lkt`l~Kw!i2W7u^z~TT*mOiEe4pEhD;RMYo*jmKWU$qFYgPD~WDp(XAr70is(~bgPMO zb1V==okNcB5oF@I+) z7VZotJG(QGaTiEgf0L2wvi@GyKggKOWZa8Vwz$bib=l%xws?>+naOw}NZAr5Bh_U~ zc-ay`#$+brxg=#vnv7JJE$L-T1{sr?jHjcNEoCxNUAB~$Efr);W-^}9Qns|oNOjrL zUbb|QF`3EurI4~^Oh&59mhrM>f{e*b#_yGsEo(ATUAC;3EgNJ^W-@*?rEEEqk?OML zyllB3V=|NR+bw0wn~YSKE$?N^2N{!@j5i1=TftEdjLA&K`>m7>Fd3;X8{lOFf{e*b##^?Ot!gq-UAC&1tr}!ZW-{LOrEE2mk?OM5 zylk}~V=|NR#UW*@n~YSKt?p&32N{!@jPD*PTf=0ex@-+ETO-Jr%w&9hN!gkvBh_VV zdfA#m#$+br8&JyDG8w5ZTg%JV3Nj`$8DFAOwzkPgb=lfpwsw#)naTJbma=tBMyku! z@v?P-jLA&KSGkm}Ycf(@wyu}08)QsoGR_91Y@o?Vb=g2K8yI9vW-?AFq-;Htk?OMb zyllN7V=|L*P9kONn~YSKt?y;)2N{!@jMEw^+rVU`x@-e4+aSo8%w(JyN!f-bBh_Ua zdfA3S#$+br zh;B>KZ6&&`MYoOUwiVrWqT60{JBV&a(d{I=bbE+yPtolqy1hlW zkLdOl-F~9mKXjau^5;+985`T3L8{*w8~ZzB<8Ws%+1Z_ejJrU}2APahmksi=K|#i3 zCgWa|vQ11zs>?R%!vQ( z_GUrGWG3V3C}o39MyksOd)eS1V=|NRjFz&^O-8EAHutj4gN(^c#xI4G4KW$1E*s)y zLxPOSOvdk(lx<-$QeC!%mu(SbOlC5EHKlAzlacDOExl~ZAY(F<@!KtBTbYbhmu=-` zTLl@DnT$6GDcjm)q`GWtFWWlEn9O9n$4J>WCL`5l+j!YFLB?bz<84aHwlx{4F5A}2 zwhb~SGa2u6QnsDRNOjqEUbbD3F`3DDvy`&!O-8EAw)e8_gN(^c#`~?5?O-xeUABXl z?GR*4W-{KgrEEu&k?OJ?y==!IV=|NRt}kUfnT%AI?c`-U1sRi>j4uu;+u3BKx@>1J z+d0UX%w&A`NZBqXBh_WQc-byN#$+br>r2XZH5sWc+ttf<4KgM(8Q*|XwwuXFb=huS zwp)-fnaTJPm9pJUMyktp_p;rCjLA&K_pp@hVKP!(wuhJP5oAneGQP^CY)_Ms>asn( zY|kKLGLvyOAZ2@*j8vEH}BG zvi*aM$mE-Jzm8Omv5f?g-HxDY~OXceLn^ z5#6z(J5F@Ri|z!`ohZ7KM0c|2P7&RyqB~7=r;F|k(VZ!}vqX2c=*|fpr=jNfi4JKSWXy6kW-J3Ppk%w)VlNZAo4Bh_U`c-av_#$+br zJx0ooG#RNbJJQRJ3^FD&8E;ckc9h9Tb=gr~c2tltnaOykld_{tMykt>_OhdcjLA&K zo28T;V=_`*c8r%D6J$(gGTv{c>{yeL>at_K?ARb@FHvXg_1$xOzVsFa;zGE!Z3ikF=dWK3o< zzK5mkRFjeFvQxe6)F5Lrlkrt9Wv7{pRF|FRWv2xhlbMXO0VzA(WTd+6bT2zS$e7Gz zoKQ&F873puWoLNV89~NmCgYq$%FZ+ysV+Oy%gzikCNmkQHBxq#$w+nCSzdNlkTIFb zI5U#6vrR^-%g*+)vxAJuOvcHTl$~QTQeAeAmz@)2Om-&Y{Px^N7SC_b6W#fuyFhdo zitZxOT`amwM0cs^E)(75qPs$LSBmZ`(OoUNYeaXg=&lpp^`g5$bT^9bCeht2x?4nd ztLSbM-R+{gLv(kF?k>^YExLO`$0;d){^Xr;uH6}=`kisEzcbDacLtN4-5JQZ3#9Bk zlacDO^Stc5AY(Fk;zDP*+pJ?3(vde;u$xOy?x0GFO zGE!Z3xtCoYWK3o<-XNsx3X_rQvMapoiXdY$lkpxSWmlSvRF_@pWmg6nlbMXSDJi?k zWTd+6DlfY#$e7Gzywge9)g~j=WmkLI)j`H&CgaUg%C0dPsV=+5%dQDBCNmlDw^DYk z$w+nCwO)2@kTIFbc*~Zu>r6(f%dYdX>w=8QOvbyulwEH!QeAevmt7xZOlC5^IHc?b zlacDO8@%j>AY(F<@!cb3H=2x8m)+=PHwGD#nT)S5DZ9yJq`K@TFS{won9O8+14`M= zCL`5lH+$L5LB?bz<4aV^ZZR3DF1y9cZV56bGa28*Qg*A!NOjq*UUqAcF`3EuDwnd` zOh&59Zu7F+f{e*b#@T?B-EJ~cU3R;d-5z92W-?AFr0fopk?OKLyzGu3V=|L*P9kM@ znv7JJ-RWg_1{sr?jMEw^yUS#xy6i45yDP|;%w(JyN!i^dBh_Vhd)eJV#$+breb=iGhc3+S&naQ{prR;u_k?OMh zz3l!VV=|NRM3AxvOh&599`LdUf{e*b#&b!^9yA%LE_=|+9t<)jGZ{}uDSOCdq`K@O zFMBA+n9O86qowR&lacDOhrR6KAY(F<@k=3PkC=>9mp$TTj|3T$nT+2nDSOmpq`K@; zFMBk|n9OASYD(E-CL`5lk9pZ+LB?bzX|9uG1mGZ}9XQuc(&NOjp0 zUiL(gF`3DDkCCz`O-8EAp7gROgN(^c#@m#XJ!LXdUG|ihJr!h3W-{LCr0i*vk?OLi zz3k~AV=|NRW+`RQn2c1HJ>zB11R0Z=jQ3k9d)8#6y6jmmdp5|J%w)V}OWAWKBh_Wk zdD(M8#$+brU0=$cHyNoed)~{Q4>Be*8DAVy_JYYsb=eDE_Ck;`naTL>k+K&}MyktR z^s*O&jLA&K*O!#NWHM4+_L7&q6l6?hGQI(&>}8XY>av%;?ByV1GL!KoDrK*jj8vDs z;$^P{8Izfe?_nu>)nuf)>{Ty&HOQFEWPFuN*=r^v)n%`F*=s?@WG3TmK+0Y>8L2LN z-OFANGA1(_ClpflhRH~E*&AN=MvyU?$v7vGvNuges>|N=vNwZ_$xOy+jg-A*GE!ak zmY2O1WK3o<&Wxn&ZIhAevbVkL?I2?^lW}q-W$&1bRF}QuW$y$Tlby*pzkRon#q-287zR5^++52AhevmPl$#^12*#{;g)ny-e*#|+! zWG3UeBxN6(j8vC>=w%-U8Izfer=yg8WHM4+_K}x;6l6?hGM>>=_OZ!Gb=k*W_HmFg znaTL2kg`uqMyks`@v={XjLA&K@0FB&YBEw?_NkYB8e~jnGJZ9s>@$;*>ax$g?6V+a zGL!M!EoGmZj8vC>?q#0`8IzfeHwY>F!epen>{CdvTuTn z$xO!kt(1LhGE!akt(ScpWK3o<-m<0aJCl*>vhTd?yC7pSlku)EW#5~ORF{44W#0!G zlbMV!4k`P=WTd+62QT{}$e7GzeD_G%k0vA4Wj}h^k3q&{CgbZ%%6>8#sV@7;%YF(n zCNmk|fKv9e$w+nC&tCR(kTIFb_!5<}Ura`-%YN~)UxJLuOvd-Hl>KTlQeF0|m;D-K zOlC5^%BAc#lacDO-@NR%AY(FG(TyRxF-13)=*AY^IHDU@bmNI`e9=uHx(P)$k?1BC-6W!$RCJSxZgSC0A-X9= zHr~H5JjOLCT@qbsUqf58u?{x3z)wgG_9{-)} z?9M>OT_9y$Oh&5Tv(4@ZS(hMVGLvyHN?BKvk?OLpUe-0pn9O865u~h}$w+lsH!tfJ zWK3oBe*8Ba$k8_8s(x@;sb8!5<`%w#;HrEFxAk?OLMy=>$l zV=|NROCe>Wn2c1HjpAja1R0Z=jNdCM>tQldUDm_PdITAhnT%geDI3*fq`GWWFB>(; zn9OASc1zi4CL`5lqj}kALB?bz;|)T}MmHI$E*ss;Mh`M3Ga2tOQZ|OkNOjp5UN%OM zF`3DDo076IO-8EA#`Ll=gN(^c#yg#qjb$=YT{f1NjTK}}W-{I^rEF}Ik?OLsy=?3t zV=|NRek*0;n2c1HjpJqG1R0Z=jJIql8`or{x@=r88#l<9%w)XlOWAlPBh_W&dD(bD z#$+bri$luBHyNoe8{f;u4>Be*8Q(opHi5}Vb=d@7HbIavnaTM2lClX+Myksu^s)(q zjLA&KH=vYFWHM4+Hj$T26l6?hGQLEmY+{p<>aym-Bfgy`4l*V)8Q;TFHi^kdb=f3d zHc5~%naTJnm$FGsMyksu^|DEWjLA&K*?^QyW-?M;Hkp@A7Gz9jGEOL@Y;u#4>axkb zZ1NyuGLvymB4tyUj8vCR;bl_<8Izfe(;6w8(qyE%Y)UViGRT0vx{yH(akBkxkNX&=;jgKyrP>=bn}aD0nsfey1t@Y zNOTK}ZV}NfD!P85oq>$IK+2{u8L2Lt#>=J& zGA1(__o9^bG8w5Z>*Zy=f{e*b#uGuxrZpLAh_FAY(F<@r;(T-X^2ZvKfMm z$xO!Ym6XkBGE!YOqnFJXWK3o|l^vN?i` z$xOyuwv^3jGE!YOrnW%GL3yg|lfCgU4W%H}f}sV*qCv)FCgbEv%KDj%RG0PhvVK9v zWM?wYZx?H1@%*;G=oS~<5~5pDbW4eDY0)hsx@ASToamMp-3p>xQFJSbZe`J}BDw*h zTUB(ciEee#ts%NKMYopd))w75qFYyV14Xx<=++nA2BO_z~vSosd$xO!Ym6R=OGE!Z(td}htWK3oz%U1ESRf3GkOvd}IlnpQ$sV*DfWdnkY$xOyuwv?@EGE!Z(s+X-AWK3o<-u0zy zHItF*vemq7wIE|MlkvqNWviQvRF|#pWvd4nlbMX~9w}SHWTd)m4KG_G$e7Gze0@pT znkFOFWovrbnnA{7CgU4W%GNR&sV-Z~%hn1qCNmjdqEfcD$w+nC+FrJHkTIFb_#T$B zbxcO8%hvI-b%Kn^OvYEal&xzrQeC#Lm#rIQOlC692Bd7D$w+nCKrb5@WK3o{d%Qgx!COeaHe!Foai|4n4M7N3PHWl4wq8lu_ z%|$mvbX$mSOVMp5x~)aGjp()&-FBkeUUWN%Zb#AWB)XkNw~Od@72R&4+g)^fh;C2O z?IpUsMYoUW_7&ZJqT4@ooRaeAPu>|D+nqtG-x(YGJ7eQ;XE52>oq>$IK*|Q0j8vBm z^0Gle#$+brUX-#;Oh&59Hu17ef{e*b#uGuxHZ>WkF5A@0HVrZ+Ga1h%Dcj6sq`GV~ zFWW50n9O869i?or$w+nCU@sdSWK3owxySC8DvamGJdawl8 zY^xw+GL!KJA!S>ej8vCx?PXgB8Izfe_ZTVL#$=?rY#T4zCdin~WV}sD*|sJl)n(gy z*|tH(WG3UCPRh138L2MY&dat7GA1(_ZrDs>^op zvK@kq$xOyuwv_E?GE!Z(qnGU%WK3o<-u0zyCzFxtvYotaryyf8lkvqNWjmXURG01S zWjhBMlbMX~9x2{i z%XSMgCNmjdqEfcI$w+nC?q0TgkTIFb_#T$BJxoTb%l7cHJ%WtMOvYEal4 zYIg>yerF8zcgE0gXE52>oq>$IK+1-hj8vBm^Ri(<#$+brUX-%oCL`5l!@X>HkTIFb zcp^yI0VX5WWe0fK0YS!OCgZszWe1v!RF@s-Wd{ZslbMXCqm&(FGE!Z3ke3}4WK3o< zp3zcvu*pbu*}-0RaF8*X$@ry^vO`Qps>=@XvO|K5$xO!Ym6RQ7GE!Z3sFxiYWK3o< zel?}+Fq4t$vctUWupnbHlkwXvWrv%LRF@s@WrqhDlbMV+2q`^%CNmlDbW(P-$w+nC(O!0RkTIFb zc(atUV@yV>%Z~A~V}gvyOvd}IlpSj_QeAeemmM2qOlC6PvZd@elacDO3JW-`9YrR+45k?OM3yzI0fV=|L*HXvoEn~YSKo$h6)2N{!@ zj1vkeJHuq8y6g-uJ0r-L%w(LCNZFYtBh_VRdfAyl#$+brv_{I#G8w5ZJIl+?3Nj`$ z8D~aPcDBh#b=lcoc6N|4naMc0lCpD5Mykus@v?J*jLFVqoZp_?$m03!d7?XCbQg&3 zLeX6$x{F13iRdmB-DRS?Ty$56?n=>JCAzCcca7+-72S2ByIyoRi0($w-6Xo3MR$wn zZWY~aqPtymcZlvz(cLAwyG3_T=r|?i&!4@a(;m%;PvpWMBcY&0h zXEIV$V=|NRHYH_O znT%AIUFBt01sRi>jCVRIyV_)=y6kE%yE@32%w)V-O4&6gBh_Wsc-b{U#$+br{Z`7Z zH5sWcyVlFD4KgM(8E@HAcAd#cb=h@Zc3qG$naOz9m$K_kMykuM_p_(H3>arWX?8YEtGL!N3C1p36j8vE1}D^!ImnpIWPFKA*)1j`)n&JM*)2iFWG3T#SjuiS8L2M2)yr-TGA1(_U*%GE zo5@IZ*==5STaYoC$v7L3vfE8Ys>^QovfG1<$xOxxg_PZ4GE!Z3hnL+EWK3o<&Pk-~ zPLq-9vOB%(&LCqllW|%jWp|m3RF~c5Wp@P`lbMV&BPqMvWTd+6ZZEq#$e7GzoLouS zJtia7W%qd5Jwe7~XEM%j?`>r9{PsT4-7mTaME9WR9unQdqI*PikBaUw(LFA@Cq(z8 z=$;bY)1rGubkBd9Ar#pGJYwf>=BcZ>as_??2#a2GL!LpC1sDAj8vCB z>Sd1x8IzfeUri}{%w(jx>@hEUEXbJ5Wc+qZ+2bZ7)n$)++2cXRWG3ScLdu>n8L2LN z!poirGA1(_?=e#Lq{&Ei*^^%OWRNkL$#|QRvZqW&s>`18vZsQK$xOyOos>OoGE!ak zw3j^{WK3o<-Yli;8IzIfvS+;PnIK~_lkt8lWzU+7RF^&LWzPl~lbMXSY$g%U<%bmx7GROvX2$l)Y>+QeF14m%SWhOlC5^M5XK%lacDO zSG??%AY(F<@jWbMubPZhm%Zv`uLc>DnT)S;DSORiq`K@iFMBP>n9O9H4M^GRCL`5l zuY1|+LB?bzOT_9!enT%AIz2{}`1sRi>jC)ba-ZvSkE_>h0-VZV+GZ{|=Df_@=q`K?_FZ&?K zn9O86m!#}NlacDO554TeAY(F<@pP23k4#3Y%Rch5kAjTJOvW=>%04z3sV@82%RUY= zCNmko6jJty$w+nCCtmhRkTIFb_`Q;{PfbRu%Rcq8PlJrfOvbOKlznD0QeF0$mwgsw zOlC5EyQS=NlacDO&%NyPAY(F<@dhDfUzm(kmwn-7Uj!MGnT+=sDf`l7q`K@&FZ(jc zn9O9nO-b2TCL`5lUwPSALB?bzNOjpaUiM9p zF`3DDzm>9YO-8EAzV)(igN(^c##^?OeP=RKUG|-qeHUa*W-{LOrR;l?k?OMVz3lrS zV=|NR#UW)sn2c1H{orLk1R0Z=jPD*P`_W{iy6i_U`!UFv%w&9hN!d>(Bh_U;dD%}v z#$+br8&JxAHW{fd``OEW4l*V)8DFAO_KV3#b=fao_Dhg4naTJbma<<>Myktx^|D`s zjLA&KSGkn^W-?M;_M4ae7Gz9jGR_91?01uq>ayRx?DrsJGLvyaA!UD`yQIGsu|CWSrJW*j;{ar{I-kex{9uw=(>w;B+-p5x=}>eLv*8x zZZy%2F1j&9H>T*u65ZIM8%K2Gif%m7jW4M8DMUA= z=%y0g)S=^)ls|v+&gkgceQ5t-9Rs^I|CImFozeVnC5HBB{`Ak*EvN23X zs>{akvN3{;$xO!El$4EWGE!YOrk9NwWK3o<-sz-lER&Jyva!5utRQ1DlksLLWn-I+ zRF{qIWn%{!lbMY7TPYjIWTd)m94{Lu$e7Gzyk$$-xF#djW#f9;xIxBbCgWXS%EmJp zsV*DO%f<^bCNmjd98xyE$w+nC_+B=CkTIFb`0kOi2~0++%O>!$34)BtOvcxjluc+d zQe8HomrWRCOlC5^0i|prlacDOiM(v0AY(F<@g*u{6Pt`wmrd+t69*ZSnT+pYDVxM( zq`GVpFPkLDn9O8+l}p*ACL`5llX}^tLB?bz<7`06CNmkSE}P8DCJQnqGZ`loQZ~8C zNOjrdUN(7Uf{e*b#%Ya|O=&VxT{fkcO&MfNW-`uh<2N{!{$vD65*~sF*|8^SD^%C8*qMJ^1(~GXR z=w=YzjG~)KbTf-?7SZ()-K?UUO?0!1ZVu7SDZ05tH@E2K5#79^n@@D}i*5nYEhxIa zqFYFG3yW?M(Jd;vexc(ml0Sd)&gf}(2C05$^z?T|&v0ij+1Z_ejJrU}rZE|*E}O>7 zrU^18Ga2`yl=U(hsV?i~Wxaxo$xOx*LCU5z8L2Lt*2|_1GA1(_&m}3F&Sa#zY&tKS zF36b7WIP?EYayv*Z2BN$GL!L)ma^U^Bh_WSy{vbTF`3EurI4~2Oh&59X7I8Z zf{e*b#_yGs&1f=GT{fea%@|}%W-@*?rEDgXk?OLUylkc*V=|NR+bv}?n~YSK&Fp0} z2N{!@j5i1=o5f_Lx@;CNnF`3DDzm>8%Oh&59=J2vP zf{e*b##^?O&1o`HT{fqe%^74&W-{LOrED&fk?OL!ylk!@V=|NR#UW*Ln~YSK&Fy7# z2N{!@jPD*Po5y6Nx@;aVnl~Kw!i2W7u^z~TT*mOiEe4pEhD;RMYo*jmKWU$qFYgPD~WDp(XAr7 z0is(~bgPMOb1V==ok zNcB5oF@I+)7VZotJG(QGaTiEgf0L2wvi@GyKggKOWZa8Vwz$bib=l%xws?>+naOw} zNZAr5Bh_U~c-ay`#$+brxg=#vnv7JJE$L-T1{sr?jHjcNEoCxNUAB~$Efr);W-^}9 zQns|oNOjrLUbb|QF`3EurI4~^Oh&59mhrM>f{e*b#_yGsEo(ATUAC;3EgNJ^W-@*? zrEEEqk?OMLyllB3V=|NR+bw0wn~YSKE$?N^2N{!@j5i1=TftEdjLA&K`>m7>Fd3;X8{lOFf{e*b##^?Ot!gq-UAC&1tr}!ZW-{LO zrEE2mk?OM5ylk}~V=|NR#UW*@n~YSKt?p&32N{!@jPD*PTf=0ex@-+ETO-Jr%w&9h zN!gkvBh_VVdfA#m#$+br8&JyDG8w5ZTg%JV3Nj`$8DFAOwzkPgb=lfpwsw#)naTJb zma=tBMyku!@v?P-jLA&KSGkm}Ycf(@wyu}08)QsoGR_91Y@o?Vby@TOGIqrO8`*(D z#$+brghI;JGa0EaThGhZ3o<4%8RsNYw!XavZzY@;A!vNIXyw;MOIcz!!bbeo87 zQ_*cEy1}B`Ty#T3w}t4o6x~*$+gfznh;CcaZ6~_zMYn_Kb`;%CqT5+?yNGUA(d{O> z-9@*D==K!dUZUGubo+>IU(xL+y8T1PDJg&cDMNOjpDFB=qOOlC6fMJe0FWTd)m6EE8&$e7GzJQ1X9Q?R>vdw~w$xO!6QOX9Jj8vBm_Oii2#$+br87*a-n~YSKZSG~82N{!@j9&^V z8)7n2T{gtah6EXtnT+2nDciziq`GViFWVx>n9OASYD(FbCL`5lTYA}+LB?bzwhA&PGZ}9XQnt0pNOjrPUbc0RF`3DDkCC!%Oh&59w(+uUf{e*b#@m#X zZEG@8UAC>4Z5w1vW-{LCq-;Brk?OMTyllH5V=|NRW+`Rcn~YSKZSQ5<2N{!@jQ3k9 z+rea{x@-q8+abu9%w)V}OWBSlBh_U)dfARa#$+brU0=#}G8w5Z+sVsz3Nj`$8DAVy zwzJ7db=l5dwsVj%naTL>k+NM(Myktp@v>cljLA&K*O!#-YBEw?wyT%z8e~jnGQI(& zY&VmU>ayLuY_}j|GL!KoDrLKyj8vEH?q$0N8Izfe?_nw1!(^noY!5HnBgmM{WPFuN z*`6jN)n$8n*`7hhWG3TmK+5(q8L2MY%ggo(GA1(_Clpe)x5-F#+1_5ZcaSle$v7vG zvVBZOs>}BAvVDS#$xOy+jg;+cGE!Z(ub1r`WK3o<&WxmNKa-K_vi-blzaV2WlW}q- zW&4|qRG01VW%~yilby*pza84h;`!|`(G3^f0irulbO(vs7u^Y>J5h8eiSA_4og%tZMR%I$P8Z!7qB~P`XNm4?(VY`I zPD%OmC-01*c4v_4cg9eEXABK@29urL8OXQ`q->bUNOjpTFB=wQOlC6fMJXF@GE!YO z+{=as8IzfeCxVn6U@}r&c7T^15M)edGM-CPcA&{fb=iSlc3_Y(naOxMO4&gsBh_UG zdD%fh#$+br87*Z8n~YSK9qeTX2N{!@j9&^VJH%w9y6g}yJ0!@M%w+stN!g($Bh_Vx zdfA~t#$+brS5wLkGa0EaJIu=t3o<4%8Nc09cDTt%b=l!wc6g96naOyAkg_98Mykt> z@UkOcBGdb8DvamGTx@7>?o6w>awG}?5H4PGL!L6CuK*Qj8vB$ z?PW&?8IzfeH%loy#$=?r>=-XQCdin~WW3)>*|8=g)n&(e*|9;!WG3S+Tgr|z8L2Ki z&dZJqGA1(_@A^`9yvay)+3{X>e2_7j$@t=svJ*^3s>@FBvJ--g$xOy~kCdHgGE!Z3 zqL-Z*WK3o0WkvkTIFbIH8cTGfYOR%g*q!GlGoCOvX8hl$~iZQeAeYmz^18OlC4pYozQflacDO zv%KuAAY(FqPtpj*NE;~(OoCH>qU2i=x!9< zO`^M5bhn7^R?*!iy4yu}hv@DU-Cd%)TXgq?j#E?3+vWtU^ z$xOyCg_K=lGE!Z3iI-gxWK3o8*7lacDOYrX8+AY(F<@s=%R*O`n|mtE&&*994qnT&URDZAceq`K^SFS|a- zn9O8+aY)$>CL`5lH+b0%LB?bzW-`8qrR-Ldk?OKrz3kQ? zV=|NRRW4<>nT%AI-R5Pt1sRi>jI#kLyWM1@y6ko@yFJL5%w(KUNZB1GBh_Vhc-b96 z#$+broJ7j*G#RNbyVJ|=3^FD&8K*T;c9+RWb=h5Bc2|%wnaMaalCrx^Mykv1_OiQ! zjLA&K$(5AdV=_`*c8{0c6J$(wCgc3}-bNPBZ|@V`{i1t7bPtN|A<;c7x<^F!sOTOO z-Q%KrLUd1x?kUkdExKn!_pIoi6W#NodqH$BitZ)Ry)3#{ME9!bUK8ExqI*MhZ;I|M z(Y-CYcS6T0DS!UtopG<-8KnB1aj(BK?hSValbzie$hZrn>^_r`>azR1?7kplGLvyH zO4X>z1R0Z=jOUV+J!mpgUG|`tJs4z6W-^|R zQudI^NOjpmUiMIsF`3DDMoZblCL`5l4}00eLB?bznv7JJJ?Ujn1{sr?jJGK%d&*>_y6h=0dn(A7%w)XN zN!imTBh_V3d)d=L#$+br%~HyqF&U{Yd&bM22{I-#8Sl4J_N>WBb=k9C_H2+bnaOy| zma^weMyku6^RnlHjLA&KyS|h?Z!%I{_Pm!pA7o5sGQK#Z>;;pN>arKS?1dm>GL!M$ zBV{j|j8vDs=w&Yk8IzfeuP-Tk$z-Iu>?JRIDae@2WPAfk*~=y))nzYx*~>x3WG3TF zRLWj48L2LN#mim^GA1(_-@{V&s>w)o*{fdmYLGFR$@nUlve!&Ts>@#Uve$x)$xO!C zfRw#%GE!akx|h8kWK3o`TvNyc!jUZz(lW|TWWpA2{RF}Q!Wp4%?p`vJZlc$xOy`NyH$Yi9t>?1GxD9D)1WIUs#>|>LW z>avf$?BgI~GL!L3A!VPKj8vC>;$@!%8Izfe-zzEm)MTW(>{BoMG{~6DWc+GM*=Hsr z)n%V~*=IqGA1(_ZxB-Ug~>>D*%w~+MUXL>$#{>EvM)_W zs>{CgvM+;-$xO!El$3pCGE!akm6v@LWK3o<-sz<5Ym<@cvah}D>mXw?lksLLW#5>L zRF{3@W#0rDlbMY7TPge2WTd+6TQB=I$e7Gzyk$$-cP1m%W#4((cR|KvCgWXS%Dy)l zsV@88%f1gXCNmjd98&g!$w+nC4_@{|kTIFb`0kOiA5BK8%YO8-AA^j^Ovcxjl>KBf zQeF0wm;DrEOlC5^0j2C`lacDOpS|qoAY(F<@g*u{znF|vm;K^pzXTbRnT+pYDf`uA zq`K@^FZ(sfn9O8+l}p)gCL`5lzj@hjLB?bz<7`06em5DZF8kffeh)GxGZ`loQuc?* zNOjpCUiL?jF`3CYCy}y0O-8EA{`9gxgN(^c#%Ya|{be#zUG|rk{S{_@bLYbQ6khBGFAOx=BPgspuvX z-Q=R1LUdD#ZYt4D9Xd`)`SU05jE-*IhxQ-VF|cd%Px=4c86Dl+|82{TZrz&yLzmq< zdiCwutH*yQJG(QGaTiEg7n70d_iPt0>k?#4W-{(YDeG!7QeD>7%en>`lbMVsf|PYL z8L2Mo=4IW2jLA&Kb4kj&n~YSKb@#IFLB?bz4M;lacDOalCAtAY(F<@s=%Rn&n9O8+aY)(tCL`5l<9pfoLB?bz8n!T{f|oO&nxQW-`8qrEC(D zk?OKZylj#nV=|NRRW4c9b`;)Cgc3JXCsUM{@ZCp*GqKMif%g5O)t9M zqMJc`PLvgw12$xOyGTFQExj8vEP_OjkV#$+brmqN;BFd3;Xo59Ow z2r?!!8NXLjHlxW%b=izwHe--6naTLol(LyjMyktZ^0JwNjLA&KZ?}}qY%)?^HnW$_ z9Ar#pGTtDhY!;J|>atn9Y?dHnGL!KhBV~O|MykvDcv+tyV=|NRHYH`Vnv7JJ&FW>d z1{sr?jCVRIo6TgTx@auyfY@Q%vGL!N3C1vxPj8vD+>t*u>8IzfeZ$K%V&t#;!Y(6iW zFUXk8WPFKA+59FW)n)U0+5ADqWG3T#SjrYK8L2K?z{?g0GA1(_U*%G^pvg#e*@9lS zV30AH$v7L3vc4uG)n$FXtZ$GpnaMbzkg|nLMykse^0I}3jLA&KIf;}lY%)?^wy>8i z9Ar#pGEQrxY!Q=@>as<=Y>^;iGLvy;BxQ@5j8vB`>Sc=t8IzfelPf9fXEIV<*3ZlO z1sRi_$vD4VtdYg@+y0_kTy#r_Zb{KCCAy_Ww~XkP72R^8TV8Z4h;BvEtt7gYMYoFR z28eD|(XA%B)kU|4=++e7TB2K9bnA$2UC|8`-Fl*1UvwLYZbQ*+6go~x`SU05jK%EE zAl2`T#r&PIShzEo?Cj1!#$6y~{Y^%y%ldm+{~%*BlW{Lf+2ST6)n$u&+2TRQWG3T@ zAZ1IKj8vB`;blt%8Izfe=aQ5yX);n>wxpLW8DvamGMarEQY=t0WGL!Kh zBV{X^j8vDc=w&Mg8Izfew<#%G$z-IuY$Y#SDae@2WW3W!*~%s()nzMt*~&r2WG3Uy zQp#2_8L2K?#miO+GA1(_@3&Gmz+|MlY=D;y2r?!!8E@HAwyMcUb=j(3wrY?unaOz9 zm$KDNMyktJ^Rm@~jLA&K7l)LsZZc9`wz`+C9%M{rGQNAHYz>o<>asPwY>gmeGL!N3 zC1q=xj8vDc>1AsM8IzfeZ$K$q%VeawY%MQaE6AA4WPFKA+1e%})n#jY+1f$IWG3T# zSjyHh8L2K?$II3UGA1(_U*%G^uE|Js*}7h~ZjdpV$v7L3vVkTe)nx;{Y+#TvnaMbz zkh1kmMyku!^Ro4VjLA&KIf<05Z!%I{w!W9GA7o5sGEQrxYy*>#>aq>IY=a;d==K)fKBC)Kbo+^J|Il$t%AY@ZXKZYD2C05$Z0zrhjl-S6WM_8sV>{Z%QguzCNmjN1S#9pWTd)mQ!m>z$e7GzJeQatzDY?mNoGL!N3C1tysj8vEH>SenI8IzfeZ$K&A&19sy zY&S34Ey$S6WPFKA+3qGI)n&VT+3rEcWG3T#SjzS=8L2MY!^`#vGA1(_U*%G^r^!fl z*`8juXOJ}BBvb}U zW&3;C{z1lMXEM%jhc>c!emhKb!$o(1=nfR!L83cYbccxUP|+PGy2C|xgy@bG-BF@D zT6D*V?pV znaTLol(NH2MyksW^RmN&jLA&KZ?}{kZZc9`cDR=v9%M{rGTtDh>arue?1&&^ zGL!KhBV|XLj8vB$>19U-8Izfew<#$*%4DRv>?kigD#)13WW3W!+0iB=)n!L}+0jA9 zWG3UyQp%1o8L2Ki#>+XT$#~0_vg1rfs>_b^vg3k` z$xOz(zLXtrGE!Z3yq6sxWK3o zEuyl$1Yz^3FKd?hI1>&N$cK8Rv#OgUQbB3}oB| zQg)unNOjqHUUpuPF`3D@7p3fclacDO^S$i+AY(F<@kEfa3rt3;%P#P;3xbTvOvZCb z$}Th+sV=+le<-;Ns4J`O{rj6xKoo4mL{u!qE(}Dm3tKS|6%p)i1-rYuySux)ySuyd z-Cxi9d*+<`e~mfE;~0mty{25}b8xTwUTeuYUUpH7jLJ&J(^1MUZj4D;aMJDZ8{y#;5knF7>iYTVzyLGTv8Gc3GQ@Pwkgo z=4F?)$f&Gjyfvll@-`Wt+Aq7@%Pwz`QCZ1&cT3q7Z8AQ!Uv`C;UC|<=vXZfbkg_Y= zWPEDB>`E`YvPDK^C1W2WWmmPy_|$&cRbF;gi;T)j#%@Z=u5Od@sr|C6z3l218I_fc zJ)M+Y(BV{+W$@tWM*-c({Q;UqsO2+9+%5H9x@u~f?o4xGj78#Y5j5DB= z-O?uGQ~PDNc-bv2GAb(>Cs8T8wN1vS_RDVdvRhkZR8}(1!%}uzn~YEGm)+)Nx3$Qq ztYnYozS%HW{DVFT2~z?rxD$S;@FFlCpc+ zWPEDB>>e+>r$t6(CFABw%I`{?DCbGvx_Jqiu6xmZEds<}Ai0oOBJtwl~MfQToUKH6&B70e6uZZkbk-a9e z*G2Y*$les$TOxZ~WbcUV-Ik17Qoet3W!%@cGWgW~m2sb68TYlU3@Wpgfs9okW%swq z_|$&c{a$u|i;T)j##)rJ2ijzOYQO9OFMFUxMr9@Ai6CVUw#oR^e%XUw_F#*Q%1Xv_ zNy;8-lkutjvWL9vp%xjHm5isOls()g<5T-(4}00eEix)A8P8}bd!$Xqr}oPp@v=u+ zWK>o%-V{>yXq${r?Uz03WskPVsH|kXucYj;HW{DVFMG_(9&3?NS;=^7O4;LWGCs9m z_PCcl-Xf#2lJV}AvM1VPd}_b!2`_u1MMh;MV+SE+PqxYU)PC8MUiM^*jLJ&JK1Rx( zYLoG){j#UL?5P$Rm6eR$l$1T)CgW54WlwwA(=9S8D;aw_DSM_(#;5knp7FA0T4YpK zGIo|y_H3JsPwkgI>t)Zj$f&Gj>~E#)xi%S}+An*~%bshIQCZ2@WlP!fZ8AQ!U-rD0 zJ>MduvXZgam$DbyWPEDB>;*4-p+!bzCF8^)WiPhL_|$&ci(d9(i;T)j#<@qzUTTx^ zsr|B-yzHeG8I_fc)0dRJ+$Q5w`(-bC*~=|5Dk~XhKq-5rO~$A8%UuoYVwO{tSm%ZL1 zqq355Hy~wiw8{9?e%Tvd_C|}0%1Xu!g_OP7CgW54Wp8@fn=LXbD;f7BQubDxj8E;C zz2#+ZwaBQfWZc$B+1qV0KDA%=wwJx#BBQdBac3lD@3hJI)PC7JUiMCljLJ&J&6SkB z+a}{v`(^KX*}E+=Dl-}Px9>G&t@pR@i|hlDeJHYzME0@BJ`vfcEg83H_~ftLdu_jR zd}{x%+-&-mBs7=PF_RBuTdwE&u&BOa1rb9{>G#UZZnk{N{W*djCuQ_aD^!II3%}=D+@z@ngqEhku)Y z@Nt6Xzq)m8{-=&VapTU3p6Wt2e)C@wHvgh6kT**vY5v$~Ox)<&`0MiTUs!dsp@WA1 zmyc~G|M$l%9XmHa&eCCAb8>&@IsW?@bN=_^0ssBAlQ(8=%-Cr3Tc%&%wMTC|a_2s) zjoiJ@n%i$XV!QrpjvTe?HY0ZG+i%|a`VF3`G{uYEUYcK7-GM&Cxa=6^~4hVVk{-=Tkp|NHs> z_wOAV>og}_8~ZkYLHOhLo<0BjcmAB8^~LaM+y1=yi_~Fk_y5fs9eezjfA-(s34Zx_ z@$yIOimjn9BAZxb zlZb3mkxeGD$wfAW$fguoFOf|pvfd(_T4d9RY+8{`C$i~9HbYDH_kT;h(R@5C*!;%* z@6S3krf)3t|Nj}^!=8P$#`Z;+4#Av`SLVb^L^}A z!1yL(pk$O+GM?O0)+LuU-|Z&r5@cOW#z4s^uVlPGq^xT$Yi=V<)-}kwnv8*xQC`V- zi%D6xT-MFYx&>J`lQB>-$}1V~QYo7tmo+zT_G>mlkWFAR21-VGCF6}QW!-aGcQ5N6 zWZg~1K*=btWb7-XY{FdD+{4+g*@Qthp~)C18ReCX-Hnt@l*=aavWbFhB9k#tGRi9% zdn_sIk;|Iq^xHyYwom7)-%YOC#ojrzr6+nC8NBOvEP)k z=2!ZC8NBOvFnwxNpjgFUN%XPO=2AT zo77|sl#KF9#tA^mCd*}$dD&z^Hkrv7C>iCIjB|*TO`gjp_p-@@Y;uz^P%_FZ8K)sB znQXj! zE}PoRrVg^HO~ydUD6eGP7)aSPxojFQnugGQ+*~}uFMP&U%Hmk^H6WQz{n?q!C zimboL<`UTekhuHe-;@Xfg&$MtLP; zElOG6T-Mji`UY8FlQB>-$}1U91Sy*-m(Ap5GX>d9CS#yvlvgsIOHwv-E}PlQW)8BM zO~ydUD6eEZ9i?oRTsDiB%@Sm@n2dpvQC`V-MoU@q9}{M0_kLd1FUb0tjDeC-Udeb< zNLlkAT4u6Yy=>MXo7H3tl#KF9#`{Xjn*RtjlQsW=t-a}I3$ocv#z4s^uVlP6rL6f6 zbTirPUN(D>&2BOVN=A7liCIjD3uh_0MI^-~V9Wv;INW-((DwjPgpxZc57L%4KtT*<3+3m&q6?8ReCXJ)M*d z$Yle(Y(S6=Fc||Sqr8%_vy`&AbJ^TpHg}NCZ88Q*MtLP;e=BA4P$+#zxvc+@R;$F6RkS%U921-VGCF8b6%9hAw zOL*B5LAHd+7$_O#m5e(hDO)m^E$L-T2HBD(W1wV|S2Av{q-;ioiEKlWZ6vadMYf5^hPPzglJfnND`TmAWh~`a z#!?M`f4h{e3X$R?KB9dfAFWwxY=xC>iCIjCZ$`t(41F^0JkJ zY$cO1P%_FZ89N9mTRE4l>}4wl*~%tkpk$O+GWIc2wn{Er#miO+vQP$=K6L*=o6LH7{E&$W}8M10|!plCiUtvek3h>Rz^bkgaYq z21-VGC1ZapWkYk>P%j%AWJ68HK*=btWbCq~Y>iyDhL^1oWNVm=fs#>P$=K^l*_yd* zO)pzB$ksF&10|!pl5ygYvbA#AT3)tRkga7h21-VGCF9&9WozfMwY_ZZAY0pH43v!W zO2+9+%GSwc>v-8ZLAH*`7$_O#m5ei>l&zb~*7dS=gKS-sF;FtfD;XzIDO)d>t>*HHuSO$gKR^SF;FtfD;f7BQnpbp+sMl{ z3bKt%#z4s^uVmcTNZH1@Y-2CmILJ0O83QGwypnNeBxRfAvQ4~flOWr~WDJyy@=C_d zm6Q$7Wy8H}c#sV@83QGwJd<&MyJ=I_dVf1YWSfa>bCGQ!vXLU&Qe>kQmh{Qs7* zJ;)Y8wuQ+UC>iCIjHjcNjm%{uy=-KVjWihpC8NBO@r;(TEpyqHUbbbBZD}$FN=A7l z<4qxDqjK3OFB=tPqfEv?$tbU6ysxBet6a8~mu(efTbYc3l2KmCcxy`8=v+41%SH#; zXp=EeGRi9%?`|m@lgq|<*_a?3V=@LxMtLP;2O(u!=d!K6Z0jJ~+GGrrjPgpxK1Ry6 z$z|Jk*)~D8jma1&8ReCX-ISDVo6ENKvTcKGTaz(RGRi9%dparGE|+cRW!nYWb|zz> zWRzDjc9v4MeJ~E!Phg`OUm+cT_JD7}tl2KmC*kw!Ej=5|{ zFWWK5b~G6SC8NBOvDcTfopRYuUba(^?PM|rN=A7liCIjMJBt?V8JW^|D=qY*&*pP%_FZ8D~H#+bx&v=4HDD z*={Cdpk$O+GESmWwtFty-OF|lvfWL_K*=btWSobkY>!;l{6FZlbHE-!wui|WC>iCI zj8nOk?U~E=^s+sJY)_LhP%_FZ8FvFxwpT9O%ggo(vb{{kK*=btWZY0l+1|NqZ!g@blXF0vy;cBIIT64}urJ4R&3itIR%9WSyIM0TRcP7>M4B0EK7 zr;6+}k)1BGGemZ#$j%bk*&;hfWaqYI+>-MBlPhCvzB0!8l`*#A?{CN2%3z>Y2Ibkx zK*lPNvT?a=oR^IYvT-J3pk$O+GS;G$9gxco@UjDf>;RK7P%_FZ8BYW$J200W=w$~6 z*?}fwpk$O+GM-CPc2F)m$jc52vV%;u$LViWCxp!fs#>P$#_Of z*&(^?5HC9<$PO_X10|!plJTaHvO{y(pJIc$B3bLb2#z4s^uVn0|r0nQicC?or9b`wFjDeC- zUdh=-XQCdiI483QGwypplAl(J)U*|A=BY>*vmG6qUUc_m|iD`m&!vg5q$ zxF9>uWDJyy@=C@oTgr~lWygEj@j-UH$rvaZ<&})RzLcGi%TDmJ6N2mnlQB>-$}1Ts z4k|&14LejPgpxsa(oV&t<23+37)cy2%(Q8ReCXy8$UXBbS}wWoHE0875<( zWRzDjZYZSe%v^S+mz^18XPS(Gl2KmCxF?aavvS#4UUpWHoniCIj5{MKJ13W&<7MXr**PX-pk$O+GH$Mxhj zUSv0j>_(B@B(j@Dc8kbv71?bfyIo{=i0n?0-6gWSMRt$K?rq7qCFT1kSH^ky$~e!j zjPn}){`Ndu84T3Qpgda{$XEqZc7859-^P$#^Rj21-VGCF4yYWtZl%OTFyUAiLCL43v!W zO2+$2$}Y=gmwDM`L3WwR7$_O#m5jHhlwF?7F88v_gY0sXF;FtfD;e)@DZ3(}oH&I>@dz83QGwyppk}ld@}a*)?8vO^{tx1lilQB>-$}1UreJQ&k zm)+oHHw4)YCS#yvlvgrN98z{;F1yjoZVa*;O~ydUD6eFkd!+29Ty~R}-4tXunT&yw zQC`V7eM#BPx$I^yyE({iHW>pYqr8%F29&Z}a@j3jc1w`mVloCwMtLRUBr0XM=CWJ8 z?A9Q=)np8mjPgpxd05JB%VoED*=<2~o5>g`8ReCXQ@NDgp383cvfG2~c9StsGRi9% zcLP#(M=ra=%kBuWJ50tv$tbU6+)zl_ow@8zFS|3y?lc(#C8NBOaZe&;cjdCXyzH(Z zyUSz@l#KF9#%+z1-JQ$s_OiQ!>~51WP%_FZ8Fxlfc26$5$II>svU^O%K*=btWZYay z*}b{!UN5^h$nG^610|z8lW~80UsKk4e|x{k9uV1sB6~<=4~y&(kv%H1$3*tH$es|{ zlOlUcWKWCi8Ie6JvgbthyvSY<*^453Nn|gJ>=lu{Dzeu^_PWU45ZRj|drM?*i|ied zz1xy;OUn08u8jNgm2sb68TU2({q23WG8m|pL3y?^kg*D+?EYMKzn9$~WcQnlfs#>P z$yke0_CPLsz{?&8vIk7YK*=btWIPe1?7>|2pqD)uWDlB*fs#>P$#^bF*+aSPAuoF< z$R08o10|!plJRtuvWIio!(R4qkUeZN21-VGCF2<_Wsl^tN4)HjAbZ4Q43v!WO2(T) z${x*Sk9ygoLH4N07$_O#m5le5ls%Tq9`mxtg6uJqF;FtfD;aN1DSJGZJ?>?X2ifB$ zW1wV|S2Et+QuahHd&0|}2(l+k#z4s^uVm~Xr0mID_N13R8DvkIjDeC-Udh?tpMD#)HP83QGwyppk-lCr0B+0$P3bdWu5G6qUUc_m{{CuPs%vS+;PnIL<{WDJyy z@=C_eQp%pqWzTxqvqAQ($rvaZ<&}*6t&}~N%bxSH=Ys4xlQB>-$}1VWY$v8A8ReCX^RSe?mdjrAve$y_HIp$=GRi9%r*bKK zJ(s=iWv>U>>n3BMWRzDj?gpgnja>GIm%R~WZiCIjN2M1dpnoC?PYHV+1n;#pk$O+GVY9|?44Zp zj+ea?Wbc@afs#>P$+)?avUhXYyI%HgkiBa%21-VGCgcA0y{4@7{`P&5eIT+AMfQ=% zJ{H+0BKuTipNZ^qk$oYuFGcp1$i5cYHzNC1WZ#MGdy)MhvL8kElgNG+*)JmdRb;=3 z?01p17TG@{`?n?ImXzE8{)CGTv+W``hP$#^12*@wC8LofR<$UZa~ z10|!plJQ)UvX64vM_%?(kbPt_21-VGCFAKRWgq9VkG<^UAp6*443v!WO2#u<%09_u zpLp3PLH3Es7$_O#m5euqlzp1ZKJ~IsgX~k2F;FtfD;e)ADf=v!edcAK1=(jNW1wV| zS2Et3QucW+``pVu53`O2E zGRVF(83QGwyppkxk+QFH*;iinRgisUG6qUUc_m{vC1qddvah}D>md8uWDJyy@=C^@ zPRhQ?W#4$&H$nD|$rvaZ<&}(`rIdY}%f9upZ-eYxlQB>-$}1WBTPgc4mwo4D-v!xs zCS#yvlvgr#*;4j>F8kigz7MkRO~ydUD6eGf^`-2GT=s*P{Sah7n2dpvQC`V7aY)&Z zx$H+T`!UFVG#LXWqr8%F?vb*ea@kK__EV7kWHJUyMtLRU^d)6K=dz!@?B^i+*<=iq zjPgpx8Bof8$z{KI*)Ku%i^&)$8ReCXlc<#an#+FmvR{MjSCcVNGRi9%=V2-PEtmb~ zWxoa4Zzf})WRzDjPUTYedoKIk%YF~C-%Z9q$tbU6+zm+CAGz!gFZ(0N{xBH>C8NBO zaYG?xf9A43z3k5*`_p6$l#KF9#yyFY{guo9^0L2z>@SlsP%_FZ8Mif3_IED(+spnA zvcFBnK*=btWZW4^*+04LA20hS$o?@I10|!pl5uk-W&h@~f4%JAAp6&343v!WOve3f zqr?Auf7?N19YxkjWSvDep2)@*Sr?IY6 zy&8?)UB@)|Di7(A4CJrUD}(ZEWgue}NLhzm z*8FKE>kwoeOvXU1S;{LJYf;KN=CY1n)-lLBnv8*xQC`V-B1l=MT-M3UIt5uLlQB>- z$}1VqB`NEi%Q|~m=OF8BG6qUUc_riNC}rd2vhloZydWFTWDJyy@=C@tTFS=HW#fC< z_(3+l$rvaZ<&}&#g_L#4WnH|iOOSOj83QGwypr+0lCrM3tgDxG4YIB#W1wV|S2Et3 zQr0b(b@Q@rLDtP=43v!WO2)fe$|lHV6L{GKK{kQO7$_O#m5d#Ply%Q#-My@Pkaag1 z10|!plCh7GvI%q9gkCmbkWFYZ21-VGC1W=wWfSGHiM(v0Ae+c!43v!WO2(c}%6jCo z9$wZX$aP$=F#+Sd$E>}3-N*~BJepk$O+ zGIrTgHc2j<#LFfLvPn$FK*=btWbE~&Y|>mdsh3R}WRseVfs#>P$vAOH*<`tFGB2Af z$R;xx10|!pl5y^lvdMGVpiRoyiy| z8ReCXJ0mHZK9^1JWzz@Q^d@7VWRzDjZmy(khFms-m(37lGnkBll2M+?xWDbwl(qi; z+ZjdHS7bAZY-W+oBC>uWn^k19iEMU}%^|WmMb=+rbBSz#$mSN=JR+M{Wb=t^evvI8 zvIRx9kjNGm*&-raRAd81wwTBk7ugabTT*0$S~6})`Toh3(I;OSef-Mk)4|{0_OX@0 zK&=eQvz392RUlRY{np)(PRvijPgpxT9mTBxva03^$oJVCS#yvlvgsI2vRmv zE}O~AW(u;IOvXUTD6eEZm!xdwTsE_p%^YMin~Z^yQC`V-I!f6rxoj3MnkjPgpxK1Ry==d%7@)<4Mln~Z^yQC`W|O-b2Yxoj>kn=8oX zG8qFUqr8%_r<1Y)xom)!4G6LUCS#yvlvgr#mQpr%E}PrS<_@yCO~ydUD6eGfZ>4OW zTsDuF%@btvn2dpvQC`W|WlPz-xolo9n>WbjH5mgXqr8%_*O#*Sa@l-dHeZmkdWeWw_LMCINWRzDjPNGt_a4uWe%N7o@g-yml$tbU6 zoQI`skzBTjmn{-xiYzdPwP%_FZ8Fxlfwq!0_(#w_%vL#K%K*=btWZYay*`QoD$jb%=*&vfK zP%_Fh8TYqKHD#^$w}VBtw8)kb*$|N}E3)N8w!Fwz5ZQ_%TS;Uqi)pvtVloCwMtLRU z=_qB(=CWnIY}p`N)?^HnjPgpxGg``)%Vo=X*>XX)oXHp{8ReCXH-(ffpUal_vgLzp zd6O|vGRi9%?<*-=A(yS-Wh(^P3MONqWRzDj-kMUjVlG?J%T^4s6-~xK$tbU6yt}1r zrChd>m#q|JE18Uel2KmC*g;6y%DHT1FIzdtRyG*}C8NBOv5%3mRdU%XUbae*tzt3; zN=A7lV>cybtLCy*y=>JWTh(L?l#KF9#-2{fR?B6pdD&_~wwlQpC>iCIjGd*Ft)9zP z_p;T4Y;}_{P%_FZ8T(r)8=A|8dfCt*8)`BJN=A7lW0x&uYvi&uyljmiTf<}wl#KF9 z#$I2_*34yVdfA#mwx-D#C>iCIj1z~Ht(D8x^0KvpY%P;9P%_FZ8Rs4;TRWGn?PY5R z+1e&!pk$O+GEQGowoWcv$II3UvUNP$vBBh z*?PHbJuh1?$ksC%10|!pl5rlEvh{P>`d+qvkgabr21-VGCF4{sWgFzO4ZLiFAltxX z43v!WO2*xQlnu*e!@O)*kPR~#10|!pl5s;JWgF(Q4ZUo`AluMn43v!WO2$2jlx>vD zHuAEKf@~v`F;FtfD;c*nQnqm}+t|xC4zi6+#z4s^uVmaAN!cd3Y!ffrB*-=~83QGw zypnNqC1t~N*>Eo#9%RE!#z4s^&t%-+ZrYT!-rtT8*=8c!Tx45_Y^2Dx6xk?|Z6&hN zA{!&Jtwpwt$hH;Pb|TweWIKp#N0IF$vYkb?i^z5r*={1+U1WQRY)_HxC9=ImwvWj6 z71@3w+rK5_mXzQmB zg_ms+WLubwfs#>P$#^1A66*_I|_pk$O+ zGTszYHY%5m^0HAuHp*lSl#KF9#`{Xjw#sE&dD&J$ww1{kC>iCIjJKwgjm~AGy=-)l zjW!trC8NBO@$QzgF}ZAvmyHRsF(zZ6WRzDjb`VmwbuQc5%eD@(txd*2$tbU6>|>;C zn_RYymu(Yd+n9`jl2KmC*iA{iCIj9s>r?U>7U z^s*g;Y)6wZP%_FZ8GC&x+bNgr7pk$O+GEN*)wsS7q*~@kgvYk!FK*=bt zWSo1XY?oZNiP$vAyU*{->4S1;Q&$aXau10|!pl5qxUYlQB>-$}1UnMpCw4 zF5Az`_6xH8OvXUTD6eGPTuIsfxom$g+ds(mHyHyZqdb#we>=7*YrVf6C$a-XcA&@( z64}8bJ49rMitI3v9WJsXM0TXejuP3?B0EN8$BOJYksU9x6GV2R$W9X3$s#*NWT%Sk zG?ASyvNJ?>rpV3`+1VmHM`Y)=WZaVS{gW$WY`!wa`js)ZgTKEWYb%3+S{amQD+3v; zK+4ADvTU zjDeC-UdebaN!dZU>>w{YD98>n83QGwypr*Bl(K_!*}-0RaF88rG6qUUc_rf+EoFz~ zvO~P=kRUt6WDJyy@=C^=Ldp)!Wrup%p+R=2$rvaZ<&}*0m6RQp%MSCh!-DKElQB>- z$}1UfO({D(mmTh9hX>i=CS#yvlvgs|-BNZ$E<3`@jtH_NOvXUTD6eGfAf)WbTy~_F z9T{Xtnv8*xQC`W|$4J>xx$Gz}J1WSIG8qFUqr8%_o077lbJ@{ec65*(Z88Q*MtLP; zPbX!^iCIjPtORotDc^ z^Rm-|>@<@xP%_FZ8K-h7J3W`3?q#P3+36-@pk$O+GVTVX?2KG?hL@cYWM`O+fs#>P z$+)4AvNLnpnO=5gkez8V21-VGCF7n%%FfDVXL;FKL3WnO7$_O#m5kdODLXrto$Y03 z2ie&sW1wV|S2FI5r0kqrc8-^w6J+O@jDeC-Udgz*lCpDi*|}bJZjha8G6qUUc_!oj z_PnO7_5SvJkzF9N3q^L3$SxMyB_g|2WS5ETa*tqjVum4S>^AZ6$0vh%&{{2)8uWDJyy@=C^9l(GwQ*#%y9L6BWwG6qUU zc_rhCAY~WkvJ1WJ!XUfQWDJyy@=C^YNy;wDWfytbML~9v$rvaZ<&})5qm*5o%P#h^ zi-YW9lQB>-$}1VqXeqlSmtEpzmju}*CS#yvlvgs|6jF9+F1ysrE)B9vO~ydUD6eF^ zucYj~E#)x?Fahmt7ZR*O`ogl2KmC*kw!E^||bNFS|a-t~VJ2C8NBOvDcTf8*iCI zjMJBt-JHvA_OhFU>}HcOP%_FZ8D~H#yCs+1;$^o4*)1kxpk$O+GESmWc55!X)yr-T zvRh5YK*=btWSobk?6zEXo0r`dWVe}&fs#>P$vBlu+3mUPb}zd<$Zj_o10|!pl5sa6 zWq0JVJG|_UAiKk443v!WO2!R^l--%j?)0)dgX~U|F;FtfD;f7BQg&A^yUWY&3bMOQ z#z4s^uVmcTNZH-F>~1f+JIL-f83QGwypnNeBxU#HvU|Mjo*=u&WDJyy@=C_dm6Y9^ z%kK5EdxPv=lQB>-$}<`FxA!$=t@pS0i|hfBJt(q=ME0=A9ue82B700^kBjUHkv%E0 zr$qL&$et0|vm$#=WY3H21(CfdvX?~mvdCT$*{dRZO=Pc&>-$}1U91Sxwkmp$lZ4+hzTCS#yvlvgsIOH%ewE_=w!9tyID zOvXUTD6eEZ9i{ByT=uY+Jse~Yn~Z^yQC`V-MoZZvx$F@ydnCvnF&P6Tqr8&wrjW8n zbJ?R__GpkjYBB~&MtLRUeI;d&<+8`T?6Dww%w!CdjPgpxTT{v&&t;E$+2cX>xXBnO z8ReCXcej*1k;|U&vL}M<36n8UGRi9%I|wOzGM7E+Wlsj#lO|)JWRzDj_AyfSR4#kU z%bp6dr%c8`$tbU6?53pb>0I`-mpvV1Pn(Q^l2KmC*wabbGr8;;FMB4)o-r8%C8NBO zv9px2XLH%JUiNH|J!>)sN=A7lV}C1U&*ie`yzIFkd(LDGl#KF9#x7gRp3h~^d)f0r z_Pog$C>iCIjJ>{;y^zaZ@Uj;;oCP%_FZ87B@Ydoh>2=w&Yk*^4G)pk$O+GR{3x z_EIi;$;(~}vX@N8K*=btWSqXF?B!hcvX{LaWG|bHfs#>P$v6W_*(_d@#B(jf1 z_KCH5MfQ!zz7^SbBKuxsKZxu{k^LmHpGEeI$bJ>sZzB6$ zWPgb4Pm%p4vcE<4kI4RQ$+#uu`zKe%d-=+E&##R4I{5qB_iSY_P%DG-Y-J#06-e3p zx$J!}dq2qDHyHyZqr8%_7NzWiT=s#NeGp_Hn2dpvQC`V-B1qYXx$HwP`!L8pG#LXW zqr8&wT#~Yna@j{-_EC_1WHJUyMtLRU=_q9%=dzEz?BgK&*klZpjPgpxGg`_%$z`8- z*(X8viOCo!8ReCXH-(gan#(@*vQLBTQcybU+1!~z3l5C``Tm-l#KF9#-2{f zzR6|Zc-c2W_KnFHC>iCIjGd*FeVfa^^|Eh+>|2vDP%_FZ8T(r)`!1J#=Vjjo*>@&m zpk$O+GIrTg_I)n<-pjrZvhPjCK*=btWbE~&?1xP$vAOH*^jyG zM=$#^$bK{#10|!pl5y^lvY&FM5^e)Y0ngX~w6F;FtfD;eiuDf=y#{pMxA z1=(*VW1wV|S29lJQucc;``yca53=7)#z4s^uVmZ}NZB8`><=&dBgp-$}1VSHB$C>F8kZd{tmLg zO~ydUD6eGP8A;hcx$GY=`zOf$F&P6Tqr8%Fb0uZ}=CXgi?B5{!*JKQojPgvz{cWS; z|9gMiL1Z08)=6ZYMK+$u#ur%^k#!YWH<3*svhE_AP-GK{tcS>Yifm$$O(L>MMK+np zCKuThBAZfVy+k&Z$a;%xYLQJNvS~#&oyeva*$gcix1@ak^gSvxJK`eT^hX_ zjow|yH25kH>Ck92x(;jp(6KSLYcm<}zd!N!w~daSo6Dg`V<3N(UKx~UD+3v;K*~Dg zvJPI>p$Yi!zeRUA9ZbeRty#(|8Ea9>I_9#DUe+F&twdgjPgpxGg`{V z&t>C#+4w;=zR4IU8ReCXH-(gS$z@%@Fr83QGwypnN4 zA!XC#vT3|*njo9TWDJyy@=C@%iIh#7%ck|RX@hK9lQB>-$}1VSHBvTRE}PEFrVFy^ zOvXUTD6eGP8A;jnxomnbn?A^5QOJoB?Hn+&;5!t*Vn@?o( zi);arEhw^uM7FTV77^K^A{!{O#YDEa$d(Y_IlW$rvaZ<&}(gx0KD1%jWR1If85slQB>- z$}1T=2q~L0m(A&Aa|YR*CS#yvlvgtLF;dn)m-Y9u{z2B?WDJyy@=C^TO3LQSWpjDi zTtPOM$rvaZ<&}&*oscaY6(G6qUUc_m|i zD`oTKvU$90o*-$}1Ts4k?>Im(A~G^9R}dCS#yvlvgs&JyNznE?dCM76`HhOvXUTD6eFkzNBoy zT(+Q>Ef{1Anv8*xQC`V714`LKxojaXTPVmDG8qFUqr8%F5|y%rbJ@aPws4RwY%&H) zMtLRUJS=64^;a#AFPVjPgpxsa(ny&1H*v*`h(VsL2>88ReCXy8$U1n9Bxw z*}xzhXfg&$MtLRUhC<2~%Vmps*-$}1U9 zM=4u2mo4jM%Ldu9CS#yvlvgsI(NeZtE?dsamJ71wOvXUTD6eF^DWq)qT(-QIEgxjd zn~Z^yQC`V-UrE^txoib5TOr6+Fc||Sqr8&w)|9dpbJ>bswqlU2Xfg&$MtLRU-7RG+ z<+7E$Y^5Mu$z%+ajPgpx4noRS&Sfim*~&q-vdI`I8ReCXeTg?6_YVg zGRi9%yD2GKHJ7dGWvd3+swQKgWRzDj_H@1~h^<1{P zm#rRTtDB60l2KmC*xyRo&|Egu%Z3KoP?IrGGRi9%yKE_2BbTk=Worc48YW|)WRzDj z_WDw`W-eRP%hn9CHBH7q$tbU6oH(Rxtz5R2m#r0KYnhCJl2KmCIQK}|+PQ3PFIzjv z);1XfC8NBOar%<7b#mD{Ubaq*lg`y=>heTi0X^l#KF9#z|Dl z*2`tiCIjPtORt)I)*_piCIj2j9m+c1}H=w%xQ*@h-#pk$O+GVV#FY@=MZ zk(X^0WE+``fs#>P$+)eNvW;`u#$L8@kZo)-21-VGCF9OW$~MVmn|RqKLAHs>7$_O# zm5iG!DI1>4hI`rYARBHn21-VGCgc8g)26KT{&s}OHWS(ABHKb_BSp5Q$VQ25E0K*B z*%*;+EwXJywynsv6WR76+d*VIifkv5?JTlgM7FEQb`#m|BHKe`dx~r?k?k$AeMGje z$o3Q2{w*1|q`8(}gA zN=A7lV=YSAX1Q!LFWW50HZvImC8NBO@kEfa&2!o2UbcCVZEi9KN=A7l^ zw8;Evh9LwJCiX` zGRi9%J4-3sK9_CpW!neY_9kPXWRzDj_P0{DLoVCF%XSE|9ZbeR$tbU6?6Re7$6U6f zm+cs2JDQAvl2KmC*y~H#PPuF+FWV`|b}|_QC8NBOapI7&opagFUbb_P?QAjzN=A7l ziCIj5DB=?Uu`S^RnH7 zY&VlJP%_FZ87EOG+dY@P$+#Pkvb}QIUS76WknLqM21-VGCF6!d%J$A>dwbd5LAJNa z7$_O#m5h55DcdKP?c-(p1lc|&W1wV|S2AvEq-@_@wy&4%8)W;MjDeC-UdgyKlCu4B z*?wNOUy$u*G6qUUc_riKO3L=nW&3;C{z10C$rvaZ<(Z88+p$eq>;3IGksTni14VX_ z$PO0SAtF0eWQU3DaFHD$vLi)yl*o=2*)bwJR%FMC?0Au#AhHuhc9O_W7TGBxJ5^+- ziR^TdoguO_MRu0R&KB7@B0IMw~JqTJjf0=83QGwypr+mma-#q*%4lLM35a}G6qUUc_m{9A!SGAvLn6h z$RIn?WDJyy@=C@&M#_%LWk-40Q9*W;$rvaZ<&})xl$0Hv%Z~Q4ql4^dlQB>-$}1Ur zIw?CQmmT9}#{}6iCS#yvlvgr#mQr?XE<4uCjt#P7O~ydUD6eGfZ>8+GTy~t79T#NB znT&ywQC`W|WlP!dx$Jl^J3h#cHyHyZqr8%_*O#&ra@h%9c0!PyU@`_uMtLRU#35xT z=CTvL?8G2D(PRvijPgpxxkt)Q%4H{c*-1fmlF1k-8ReCX)0dQ;oXbx3vXg`CWRo#a zGRi9%XFw@CC6}GzWv2w$DJEl}WRzDjPNGtFYA!p~%T5ilQ%%M|$tbU6oQI|Cv|M(Y zmz@@5rACE5FFQTRPB$3?C8NBOaW^1kXXLUoyzGo1JHuoQl#KF9 z#tns(otewd^s+O9>`ap}P%_FZ8TTYoc2+Js%gfFRva?LaK*=btWZc$B+1a`5Y%e=I z$j&wy10|!pl5uAwW#{CwbG+=FAUnro43v!WO2*BVl%1Q)&h@f$gX~;jQpD6)%0cCpAV5!t08yG&%4i|h)KT`97wM0T~vt`XU_BD+px*Nf~1 zk=-b=n?!cA$ZiqYts=WkWVeg#4w2m{vb#ifx5(}h*}W|px1@ak=G}#B*-o?83QGwypr*zkg`j2*`;1~X^>rNG6qUUc_rh0 zC1sc8vdg^evLL(6WDJyy@=C^AQ_3#SWtV%|-$}1T=2r0WVmtE;)R|eUYCS#yvlvgtLF;aF_F1yOht_rfNOvXUTD6eGfrljoZ zTz0jWT^(dsn~Z^yQC`W|(@EJix$GJ*yC%r4F&P6Tqr8%_vy`%HbJ?|Cc5RSdYcd8( zMtLP;e=B9z<+AI%?7ASk&SVUfjPgpxE?dg3&t=zp+4Vtoy~!9T8ReCXy}p#)kjrlH zvKxZz29q&RGRi9%Ck`pQF_+!wWj6-djV5EDWRzDj&OK6gQ!cy7%Wev?n@q+)$tbU6 zoW7*&=3I8Om)#s>H=B%sl2KmCI0H)AExGI#FS{kkZZR1HC8NBOaT1lXTXWg1UUqAc z-D)xhN=A7l<2)>7x8<_iyzI6hyUk<_l#KF9#;IJ&ZqH@6d)e(lcDuiCIjJp9T zyCav~;bnIO*&QZhpk$O+GHxiO?9NP$+#zxvb%EGU0!xqklkf6 z21-VGCF8b6%I?l(cYE30L3X#v7$_O#m5e(hDZ3|^-Q#8V1lc_%W1wV|S2Av{r0m{Y zcCVM+8)WyIjDeC-p2@hsy{{>2y}!L*WDkh!L6JQqvWG?Xh{zrl*<&JmTx3s(>`9S5 zC9_IPkFvuP>83QGwypr);lCp<#*+X9TP>?-j zG6qUUc_riNC}j`lvWLCw;UIh1WDJyy@=C@tTFM^DWsi8-BSH3v$rvaZ<&}&#g_J#- z%O3TzM}zE9lQB>-$}1V~D=B*{mp$fXj|JIdCS#yvlvgs|no{<7E_>X|9uKm|O~ydU zD6eF^yQS=jT=s;QJrQJ2n2dpvQC`W|K}gw?x$H?VdoswLG#LXWqr8%_kCC#ca@kW} z_EeBPWikdzMtLP;Hzj3H=d!20?CBtT+GGrrjPgpxo=(c1$z{)Y*)u`*jL8@%8ReCX zou!mLo6DZ{vS)+rS(7nPGRi9%`&%h{E|)#$WzPlKb0%Y;WRzDjcG*()d@g(5%bpLi z=S{{y$tbU6?DeJWg5N=A7liCIjFYI8y_(Bj^|Duk>{XL7P%_FZ8Ruasdo7o}=4G!1*=r_apk$O+GEU`E_IfUR z-OFANve!+>V$A zC&=D083QGwypnNqC1vmCvUk1g-5`6{WDJyy@=V74?R!mG>;3KfBKtsOAByZFk$o(( zPek^q$UYO<=OX(;WM7KxE0KLIvTsE8t;oI;+4my*L1aIQ>?e`^EV5rj_N&N#6WQ+~ z`$J@ZitI0u{VlS8MD}k>#w{t|Ke;mA%U8yGer3GZ(cj;`XDfq&S{amQD+3v;K+4|F zW$%00`$6`;$rvaZ<&})JC}khyvJbrMgCP6BWDJyy@=C@NLCQYNWgmLkhe7tC$rvaZ z<&})*l9YXv%Rch5kAmzYlQB>-$}1U9M=AR_mwoJI9|zgTCS#yvlvgsI(NgwFF8jpG zJ_)idE0Zx$GRi9%yD2IAI+uOzWnTx`*Cu12WRzDj_H@211+g$domwg*#-yzGY{`@v)kl#KF9#)(79e#~V* zdfAUb_M^!dC>iCIjB}5a{glgo^0J?T>?e~kP%_FZ8K*BP`#G2W>}5X(+0Q0ppk$O+ zGR}Zf_De4N#mjyPvR_QbK*=btWSm5$?AKiOtC#&6WWSn>fs#>P$v6*7*>Ab*H!u4w z$bK^!10|!pl5r}Rvfp#r?_TzMko|5l21-VGCF5>D%Kpe@e|Xs+LH38q7$_O#m5dt- zDf=^*{pn?Y2HBq`W1wV|S2FHNr0lO;_LrCa6=Z*zjDeC-Udgzvk+Q#Y+23CFcaZ&U zG6qUUc_rh{NXq`nW&e2DKSB17$rvaZ<&}(^D=GUom;LKy{|4E=CS#yvlxH&TZyTNd z-}~DRBI_u!P9p0pvhhSVzR0?WtgFbniEILqbr;!$BAZBLJw(=1WD|>Q5|K?RvdKg? zxyYsv*_0ydC9~v#i);Sc_8DF_(4pvW`L4(PRvijPgpx6G6&4<+4s*)+xw3 znT&ywQC`V-E=gJET-MpkItN*2lQB>-$}1U9M=2XGmyPFT;|1AxCS#yvlvgsI(NZ>k zE*sy=#t*XbO~ydUD6eF^DWt4RF6-iDU4pEO$rvaZ<&}*0m6UbOWnI0jYmjv{83QGw zypr+Ol(KHQtecm03$kt|W1wV|S2Et+QZ_*@o50H^2(k%G#z4s^uVm~Xq^x@`>+WUU zgRHyB7$_O#m5hCiluek+CiJoigKR>RF;FtfD;c{fDVr#lP2^=01=&OP$=KgY*~GbQVlSIG$R;)! z10|!plCjH{vPp8;BwjX2kWFGT21-VGC1bBIWs~NzNxf{+Ae+==43v!WO2&yp$|lQY zlX=->K{lDm7$_O#m5g(blue$?Cik+*gKTn>F;FtfD;cLRDVrjfP2puz1lbfOW1wV| zS2E6kQZ{8So6^gs46-Rr#z4s^uVkD=rL0#j>*Zy=f~=Rx7$_O#m5lSSlueb(rt-3> zf@~_2F;FtfD;cM9DeIlfdV5*#AnR>121-VGCF5>D%BId`Q+wIeK{mC?7$_O#m5dt- zDVrvjP2*+L1lcquW1wV|S2FHNq-@$;Hm#RU8)Va(jDeC-Udgzvk+SJ>*>qkuU64&@ zG6qUUc_rh{NXn+qWz&1v^g%Yg$rvaZ<&}(^D=C{Hm(Ad1GX&WTCS#yvlxH&TZ~HW5 zt-t?vMv?Uu*-RpvS!A<_te?nc71?Yen_Xmch-^-g^%vP(A{!vGxkWaQ$mSK+5BGgRHN~7$_O#m5e8X zl+BdOX7aL`f@~&}F;FtfD;duvDVsT$&Fp0}2ieRfW1wV|S2CWCQZ`F2o5jm!39?yC z#z4s^uVg%*rFyW1wV|S2A`}QZ`pEo6F1Q z3bMIO#z4s^uVn1$q-;Pg8{lOFf^2}v7$_O#m5iOGl+B&X=Jv9=gKTb-F;FtfD;fJ+ zDVryk&EsYB1lc?$W1wV|S2A|lQZ{cco7c- z$}1V?VJTZAmo4IDiv-ysCS#yvlvgrN-$}1T+6jHWWE?dmY77Mb)OvXUTD6eGPlStX(xomMSTRg}XHyHyZqr8%F zTO(yl6OS!ek7TjPgpxospC+nah^+vL%CTNs}>9GRi9%H&;?ND3=ZLvOz&M z$YczZjPgvz{q0gsS?m4nV392?vSmazL}bf~Y&nrFFR~RxwxY;Z64}ZkTSa86iflEJ ztuC^mB3nabYl>_wk*zJVbwswV$kr3t`XbvvWWz+Zp~yB8*~TKhcG zRK7Bn@+)JhPX7LODO(u~)XJbdTN%h$1yVLRmksu^!9h0IWDJyy@=C^9l(MCB+0tIN zbdW7=G6qUUc_rhCAZ5$svSqw%nIK!nWDJyy@=C^YNy>)gvLRkJB*=!CjDeC-Udebm zO4+ixY*{Z`HprGW83QGwypr*Zma^q?*>YaCT#zkiG6qUUc_rgbA!W-$}1V~ZYf(S zm#ySwD+Sp~CS#yvlvgr#5K^{sE?e2lRt~b2O~ydUD6eGfW29`AT(*jrtrBFbn2dpv zQC`W|O-b3RxolN0TQ$g5H5mgXqr8%_r<1bPa@lHLwpx&_W-%MtLRU#35yC<+8QBY^@+$%VZ3cjPgpxxkt*@&Sh(R+1f$2 zw#gVM8ReCX)0dR3lgrlevUP%N9g{IoGRi9%XFw@iHzj;$l2KmCIF(D;2DxklFWVrICFP%_Fh8TYrFHf62%wTgZAG@7$hH^R4kFu8WIKs$XOZn9vRy^Co5*$-*&ZU>Q)GLIY;Te6BeH!( zwx7uMZ^^hN<@+aB#-{no*wn9#O*{Gf+f8j{Fi-$}1WB7%AH( zmu=%^+XUG*CS#yvlvgr#Q&P5VF5A}2whgjvO~ydUD6eGf>7;DCT(+H;Z5L$QnT&yw zQC`W|SxVXVxomqc+djy)HyHyZqr8%_zm>8ba@h`EwnLEZU@`_uMtLP;mn~&G=CU2V zY{wwm(PRvijPgpxUSG;~%4Iuw*-k;WlgSt;8ReCX6Ni-ToXd9hvYmr$XOl5dGRi9% z=N>8BC712uWxE8~E+%83WRzDjPG3^CYcAW>%XSU2T}{S7$tbU6oB^e5w_LWHm+cm0 zyP1rEl2KmCIEhNx?zwDtFWWuHb~hOVC8NBOaUPbkJ#yI|UbaV&?O`$oN=A7l<5VtX zd*-q|y=>1Q+tXwWl#KF9#@&FF?Ul>+^0K{xY%h~BP%_FZ88;MCws$Vu+spP2vb{~l zK*=btWZaWT**>{!A1~V{$o4TA10|!pl5txjW&7r`eZ6eoAluhu43v!WO2(a$lDD#z4s^&t%-+j%~_X?{CM6>;RD+D6)e@ zcCg3}5!s<4J4|GUi|h!I9VxP-M0T{ujuF|hB0ElG$BXO)k)0^AlSFp1$W9U2sUkZ~ zWT%Vl43V8Fva>{Xw#d#A*|{wlx1@akY2IbkxK*lPN zvT?a=oR^IYvT-J3pk$O+GS;G$9gxco@UjDf>;RK7P%_FZ8BYW$J200W=w$~6*?}fw zpk$O+GM-CPc2F)m$jc52vV%;u$LViWCxp!fs#>P$#_Of*&(^? z5HC9<$PO_X10|!plJTaHvO{y(pJIc$B3bLb2#z4s^uVn0|r0nQicC?or9b`wFjDeC-Udh=-XQCdiI483QGwypplAl(J)U*|A=BY>*vmG6qUUc_m|iD`m&!vg5q$xF9>u zWDJyy@=C@oTgr~lWygEj@j-UH$rvaZ<&})RzLcGi%TDmJ6N2mnlQB>-$}1Ts4k|&14LejPgpxsa(oV&t<23+37)cy2%(Q8ReCXy8$UXBbS}wWoHE0875<(WRzDj zZYZSe%v^S+mz^18XPS(Gl2KmCxF?aavvS#4UUpWHoniCIj5{MKJ13W&<7MXr**PX-pk$O+GH$MxhjUSv0j z>_(B@B(j@Dc8kbv71?bfyIo{=i0n?0-6gWSMRt$K?rq7qCFT1kSH^ky$~e!jjPpAA z``hzuWiU`H3=9zI z?oPoD?A9}WEZ3gb`>yNU^WOWMzs~2dKZMu3nBUu%XO8haL-&$5k3Jbp#;3qe)+L^- zi%!<1+GJhIlktL^j5n_|8J|Tv+0*f4PwQk)SDWnV@?^Z=CgaU3O~w_$PWDVZ*)uxX zGu0-0raT!hxXF0)N|SM2vXeatxSYo9y}WWW3-eyc(NWkS&wRy^(arq3vM#rywYUsrtD-d#go0Hlf6`JvX{z}@q(L-H?K4q zdpbK=&v>$)I$6(Zll3f5#tUvT-n`Oe>@4kMFUOO;tdqT5ZL*iklktL^j5n_|8T(s1 z*(>p6ujpj2RGaLT@?^Z=CgaU3O~x+UPWEa%*{eF)tJNlZwLBRwxXF0)N|UkIx0CgX zC+nq?^{O^mukvKP;3nhED^13U!%o&ao~*Y{*1Otdy~~sFf}4ytuQVCw9y{4<@no;* zWUp15?6vY_yx=C|%_~jD>B~;`dOX?dI@#;hCVRa+885iWc=Jk=aR#)L^@%6zqm%Wi zHd&wYWW3-et=)h2tRJQ**z$$0ZhlW`ullf4;F_NGquX0^%Q zEKkM@ZZh7y(qx>S$CJISlf7MSvbW2V z@q(L-H?K4qPblnUedEdc>STSZP1d(O885iWc=Jk=@tnj?_D($6J384r)h2tVJQ**z z$$0Zhlkv31PWEm**}FQ~yVWLpw>%jyxXF0)N|W)-$WHcNJlT6X*?ZL{d#^khFSyBg z^GcKPCad}Uw!g_fFxdc;eQ2_QCL3h3 z!6qAGvY{p$X0qWX8)34MCL3k4(Iy*Xvau!`XR`4on_#kuCYxlk$tIg(vZ*GUX0quf zn_;q^t_jDB_W`E9@QCxaKZZh7y(q!Bz>|}%E$p-6WgR4z8xI7sz zxXF0)N|SNFvXc#oCmW)Z4XHNSkn&`_;3nhED^12-(@r)to@}U2HniGgL(7x#f}4yt zuQVC=Zadkqc(P$S*|2Jp4J%K^3vM#rywYUsAnatr@4kMqvOd&>tv&=O*Xnb885iWc=Jk=vA?yGjfp24 zqmzxPHrbf+WW3-ePJaXQ(! zYLksCPsR&wGTyw>WSltcWaHz>#_MF`t4%h(JQ**z$$0ZhlX32`lTC;xo1l|Ts5aSz z@?^Z=CgaU3O~&cVPBt-~Y@$v!vD#!4%aiefn~XQFG#O_=JK3aovPnAGq-v8*Do@4> zZZh7y(qx=O?PQbV$tLS$ldDZOxjY#!xXF0)N|SLOwv$bXC!3;^O{q57l=5V};3nhE zD^13!+)g$%o@}a4HnrMhQ_GX_f}4ytuQVCY2JB?h;>o7zWYelmHmy7vFSyBg^GcKP zgu+fXJ)Ug3PBy*TWYf!&@q(L-H?K4q&q?fLGvdi+=wvghO*W%E885iWc=Jk=@wCQH zHZz`VrcO4q+GI1!lktL^j5n_|8PAODWV7PQX6aWc*p# z$>znA&C|)|Rhw*Hc`{yblkw)2CS%3e$v%oF`$#AIsM=&7l_%o`HyLkUX)@NWoos$Q z*?gUBeznQwmnY)|HyLkUX)-=NcCrQWWD9h%1=S{7P@aqz++@6YrOEgV+sQtTC;M0@ z`?%UkYI$v&wz*(c@6c)?A^n@5vZ?#K{FZzA_p2dpScB#Ewsn4QL zi$yW!*W(xEor>h&-`1^NmtWTET)fi1 z?uLtg$3}~Oyk=2%h0;2uwMwOCyESWiNQX1qpWkHv_7^uf=Dvz9BCHYycgiweEA?N04*R=Xz6+FrYexNgyp&5CihT=ea;UDuOeuV4K7+Ks1N zv+Vyz4W5qr;Gk1`0{b%jO#uvS2R>x#@O}3cH>Y1#* z$*N4&z+{V?YzdR`HLCgdv6RV{HrX;JYiP1%O}3oLmN(f7CR@>DE19g3$yPR5W0S37 zvQy<7ne(!%? zOph-e#UiZJsb%L<Qq%EoBZo9>J)!bx72ipCQbkK z*Y(0{B`{0B=2d(VTsww~k60O|t8nd7WthJ;3)hL^;saiW=_*_&0rMfYaNQU#uD4~F zuENEwpyHRBD}{v@i{auHREFs)yjTL}a%ACpFm^{Wc^0l8!^K^(4AWJ( zegfvgYT>FFF7B^on6AQA37D(5g&V|h1BK}-+#msSi?HzGFmo) z$8;55IsvmcuyC>SjqXmx-mDDMRd|^M%m&254P&_2`juh23O7u^>~bu;Yz!}}FkOY0 zO~7oWEWBI{FQ+hFg_ldf?AI*3d<-wIFkOY0Prz&nExbYuub?npg;z+x>^v>JVhpdS zFkOXLOu%e!Exb|;ucRFjbpg6!gLjGoParUSa_8fUPWQL3a^rYIoDWt)fiq?VY&*hnt(YxS$MS=UQJ=T z3a^%cIiXp2^%!1VVY&*ho`5+9T6m2ZUPEEJ3a^oXIcr*Y%@{6zPhtJ@Ii{=dniXIk zs5B|ynhzM(GFelTt!=V(Ot!Acnwe}pldW&E4NSJ7$u=@sbCYdsvQ133smV4o+2$tO z!em>TY%7y(ZL)1lwynvwGuieg+reZznrtVN?QF7LOtx!{jHeZ>KfCao#0$TPE_}MW z@S9Xv_`oc33$GQ!Ybi`u;k6PlpCk)6jp3#W(^a@>0_Njt;k9FUZH4J7ymkWSnqc8| zVt5^e=_Sp0)G2BdHx(YW-z+6Htyj~2ir!ZZG*Gs@$ zr!BmG3>W{TqOktCfzefX{RGU-z``5E@CFLgRd|C0%pJ$V8^-X43e#11!vxH2&B7bS z@J0&LRd}NW%)QgX&11N^!gLjGo`AVQTX^Fb-dJI}3U8c%*#%g5lNjDaVY&)$l7QK2 zSa{PI-c(__3U8W#+0R&bvluS^!D?as^Esxg@MZ~^O_PN;kKxS~rmOJg37DOkg|~>| zEfl7!@D>S}?Vp9WjNvU6rmOIl379>mg|~{~trVuK@KyYQ_y@Ly_0Qs>tMCp9m}8QKcZ}g36{f53jtQ8vn1y$W;hhww ztME<9d5ECOm?KnjxyQNCOgJt$C~UolO10p<1q>A&o2BH@xpJR3!kno{1z1! zJ}`^i!YyOCrNVR-Zkd4jBw4ss47XC4uEMPnFdtV7?-s+mDNI-4-4Za@1Piy0;noV% zRk(Em=E7s)-D7xnh3P81djjU_X5l?zcn^i?D!fMm<`Qb*J!5!Jh3P81X9DIrZQ;FQ zcrS(ND!f+$=4N2wHZj~rVY&*pNx`EWCdV@2@ajh4)XuY&9%=Knx$CFkOWY zNWko8EZjDR+bT>~;kF5wO_PNWjNtmg%640LlmZ~@F5A9jjV+ajp0KTrmOIw37Flog%6A2!xW~g@L>s2*@-4Q$z&&+ti8!jG1;jmJI!PrOm@1- z&M;X=lbva@vrKlj$<8s^xh6Z$Wapdg0+U^6vWrZ1vB@qm*`+4C%w(6F>BPf5UBLM?o1446gz+9&-d|C{jrZ8QFPfNhu3@qFshC3)sSK$r` zm^+SzPmkf#6{f53=?R$InuX7Z;WHGbtMC~Kn0u#%JH~KFh3P8XF#&Ufw(yxTe5S&5 z6+SZovkS2BSuuQ;!gLirD*?0Bu<+S2e73@L6+Sxwv!Ai>IWc^W!gLirCjql*vhcYv ze6GTD6+Slsvoo{sc`8e4)a06}~V5vsKMLSVY&)m zT><9#Os4{_`Fy6c$*wWkwI;jHWY?SQ29w=rvYSkHv&n8T*{vqK&1AQm><*LNX|lUa zcDKpyG1UYCVR|ekDKfXlRa4@<1q>A&o2B<@xt$< z3!kno{7w}XJ}`^i!kuHdv%+*0?wo-6Bw6^H7`{efx(Z*Dfcdyu_}Uo0R$;mdUz>or zCRq5o7`{$nx(Z*HfVuEk`1%;WUSYZlU!Q=vx>@*!7`{Pax(eTrfVqTP_{JE%QDM3Y z-g-<*KC<5>8X7`{bex(eTtfVr(%_|_P{ zRbjdc-8tjV4;+4Cmr zYO)th*3D!unykCYdYJ4bll3&&%O-opWUrd6m&tmY>@}0UZn8cmd&6XJn(Qr;y=}6- zCVR(Z@0#pAlf7Rf<1q>A&o2C@;)VZ|E_}MW@Sm!%@PS$67VZ+mT@>Nh!oqLG@LLMgRrsw0%n8QAZ^!W43e#2i?F7s*$-;eOxUa%=74DmWIg45Nofv*c zVY&*xlYlw!S@_);epg|-3cs6xIaONty%>H^VY&*xmw-9CTKN4KeqUj_3cp_g=J`y& z0Ld>FUDoS7G4; zv&b#nKZg4&OjqIl37Aikg+GYl4-}@W@CON)kE?|T#P9%x=_))R0dq~T@P{${p~7?( z{xAV^;j!?*7#^rFU4;iGV6JW!9u&ia6sD{2paje%)WU;fc(B5B6&{>`xlUVnNDL2A zn6AP@5->Ld3lELqp$gMgcxVFVj$`3rF+5CRx(W|Vz}(g>JUoVnD@<46;R%>~r-etv z@Cb$JDm)?qbAz_<$QT}}FkOX5CSY~}79JJDqZFpA@TdgLR>Q)hV|cW}bQK<*fZ5Mj zcuWkBQJAj6V-hf%CJT>^;js$SRd{RyW@l#MaWOnjVY&*BOTcXZEIdAj$16-%;qeKW zJ*I^x#P9@#=_))S0ke^{@WdFNs4!iHCnjKa%NCv#!;=)ItMH@*%$D53lVfZv+&Fqo~bZhg=Z#UPL&p(6~nU>rmOI*1kBOZ!n0#| zw!(B3o?QXv`OKUGuK9dsuF2+^>?4!SH`xM{eQdH%O!leCJ~P?pCi}u^GDBZn8g2_NU4IGTGlXG9Ht#{_Mh^ z6EFNZy71}h!k<%N;RCbCEj%}d=PFEB;kgNzPm+b_#qd0Z=_))g0rPRS@JBKHk-~Hp z{wM)+O|bC%7@n^%U4`c-U@klsUJ%0z6sD{2f&|Rf&B7nY@W%?%Rruos%q7&qpTzJd z3e#2ilLX9l+QOg4@TUsXRru2c%+0{UpT+QJ3e#2ivjof?$HJe-@aGECRrvD+%x%rW zU&Qbi3e#2iiv-NQ)52fI@RthHRrt#U%njPYU&ZiO3e#2is|3t0z`|e0@Yf2{Rru=! z%vQs~-^B1Y3e#2in*_{$#=;9@c%i~{6<(Nt*)&=B+Zg^#~A)mVY&+cn1I4DVY&+cT><9#OsUp?KA)*&vf3uAW3sv?Tg+tj zOjh4yRVHg-vc*ldgvpjP*-|E3+GNX^tf9%4HQ90|Ti#?Vm~2Iptz@!BCR^ELjZLD01wsd4T4rADPvqmk{~a^By6{W2>UE;ssCMa?rmgB+QmQI`)wFq4}ZQ-S2cqxVHD!fzzW*1=LrDJ$$h3P81bOL6pVc}(Bco~K1D!fbrW@h98LJY5gk>Il)+Xl^9+{VY&*hl7KlT zS$NeLUR7bb3a^@gIg43%wHRJaVY&*hmVi0%S$OprUR`0j3a_4kIaOMCjTl}-VY&*h zk$^e6T6oPEUQ=PZ3a?oK=J`yM0YS>zU8D~8unn6AQWC15^D7H%5DO%Ne&Utzimub+Uq8CZCO7~Vi(x(aWQfVtyXc*7Xp zP+__XZx(aWUfVp>CxOogWSD3EC%@Z&;XbW!~!y7A1SK*BlFuMQ? zZxX|sC`?!3O%gC$4GV7?!<#BhSK&<)F#8z`Zx+LwDNI-4%@Q!1CJS#K!<#EiSK-YQ zFgr5~ZxO>=C`?!3EfO%>KMQXe!&@p$SK%!aFndf3ZxzE^DNI-4tr9RBSqpC+!&@s% zSK+M_FuP?7Zxh4YC`?!3Z4xkBatm)8!`muMSK)0FF#CQBZx_ScDNI-4?GiAD3kz=_ z!`mxNSK;jwFeexb?-0W~C`?!39TG6dBn$5t!#gTWSK%EKFlR9f?-av3DNI-4of0qy zJ`3+0!#gWXSK*x#FsDij?-IkiC`?!3T@o-yR}1eN!@DX>SK(bNz&xL6QNT5y&$KjI zE0gVJveqWs-DG>1Y)_NzWwJIV+uLOOm~3B@?Ps$6O?H6E+M4V@lO1HTgH3jb$qqHy zVJ17=WJj3nNRu69vZGCQjLD8Q*>NU2zDCAl64swx_$}gv-$EBYU0wJsDlB|p7P*C6 z#&Ao8=_=eZ0rN?+aH|+@r7&HETP0vVt`^=chIdn#uEM(|V6F)kZXLtLzbsW)|NKKF zx(c^Wz+8ANyn77qt}tDNcTd1v-7LIE4DX>ZU4{2Zz+6Htyk`vWsW4rI_e{WCr!Bl! z4DY2dU4{2bz}yTh+$M(GC`?!3HVK$Jj)nJ*;k^~6tMJ|lnA@6#_le8;e8dRtMI-Fm>aZ(_lx2E6sD{2ehHXefQ9#u;r$h+tML8_n5~9|4~XFd6sD{2 z0STD>jD_3Aa9f4xD%>^!vuU#MfiZlb!gLirFafhOv+zMNe2~I)6+S2dv;DL1!7+TW z!gLirI03WAwD2J@e2Bty6+R>Zvyrv%p)q`@!gLirGy$_)w(wywe3-&?6+SEhvn99i z;W2!;!gLirJOQ)sx9|}$e1yVu6+R*XbGWeZkuiLv!gLirG68dfvG7qbe3Zg;6+S8f zb4;@E(J_3q!gLirIstPQv+yx7e2l_$6+R{bbKtY^u`ztC!gLirHUV?0wD55;e4N5` z6+SKjb9A-v@iBb7!gLirz5>kinG*`Q=JT0$COgq&CzD5XtFa+c9zM`HrY8QJJ)3Ane2R%U0|{cO?HvVE;iXECcD&RmznHxlU-r5D@}Hl z$*!)E@tB15XBYkn@xnhr7d~BG_$O3Y_`oc33%85mb_&x~xLpF~lVsr&WB5dc=_-6; z0_Njt;ge$cB!%fJd{P4Dnqc9RWB6o+=_-730_MVF;r21yUSYZlw@<)a-7I`c446cz+6Htd}<7zsxV!JPffsFr!9P1446ez}yTh+#!ZLC`?!34hfh$j)hN; z;nNkStMKUwnA@6#&xqkO6sD{283~wsr-eJla7TsdD%>#vbAz_Qh*)e>!!gLirI{~wwvG6%De2&6&6+R~cvuU#MxiNgM!gLir zHvzLVv+#K_e4fH|6+SNkv;DL1`7wOH!gLirKLNAHwD1Kne1XDr6}}(=vyrv%g)w}g z!gLkBFafh$w(vzUe38O*6}~6|vn99i#W8%b!gLkBI03Wox9}w~e2Kzz6}}_^bGWeZ zr7?V|!gLkBGy!vhvG8Rve3`;@6}~J1b4;@E~53YW3qcqcAv@a zH`xOwd(dPLne1VcJz}y)P4<|{9yi$&CVR3*#$yuJpI!K!;)UNy7d~BG_?;>&d|(#2 zg*(S^XNBo1+&KaBNwV-YF?@}}bQQiP0rPRS@U<~~t-^E_zBU1KO|bBFF?^lEbQQiX z0dwK8@bxi#y~1=AzCHnSb+hmdF?@r=F?^T8bQQiU0kdhc@ZB+dx59K4zB>W4GqdnLF?^50bQQiQ0ki$H@Vzm7uflW{ zzBd80$F%T$F?^rGbQQiY0ke^{@cl7-zru7CzCQu8Tek26G5mnSbQOLe0kb8y@Pje@ zpu%(&elP*E@3-(nG5nCibQOLm0du&p@WV0uu)=f|emDVhg0b);G5mg#xaRYjE+%{0WY3uFS(80yvgb|K)nqT2teeSRG+B3( z^)T5>ChKXkmreGH$zC;CFO&5)*=r_y-DG`C_J+ycG}&7wd)s7vP4jrmOJN37C(o zg`bJxXB4KZ@G}XRYl4NJjp1h%rmOI?3789yg`bPz=M<)^@N)^6tDA+NkKyMPrmOJt z37AW$g}cUZSB2>++%*Anowo1`G5mtUbQOLf0dq63aJLxlrZ8QFyCq=mI2L{}hF?^e zuEH-SU~X#`?jFP46{f3j_XNzn)51MsxQD`Y74DIMxj|d_r5JumVY&*xlz`auP97c;a3tcnsy z!0eVS+$V#6-;Cim6{f53n+cf1g@xaW;kOi~ ztMFS1m=lbJ-;Uw86{f53+X{#a~8AkJ2Cu@!gLjWCjoQdv+%ny z{I0@u6@E7XbE>rPdolc;!gLjWF9CCOweb5f{Jz3;6@I@0%=4Lk1zhv_On;MoV6p)w z`_N&Sc|FHo;^QO*YA7lT9|o zWK&Hx&1BO}Hp65yO*YG9vuk8LCSm>Ah2Jk;`2BR@)76FFufoCyW|3RCe+>6mn6AS8 z6EL473x5#9A1F*$;SUlpA6E+xh~WVW(^Yst0_K`v;SXc@Lxt%o{9yv-!eilqF+5OV zx(W|Wz+BxdJSc_-DNI-4K?#^ksD%f|@L+}MDm*v=bDg&EkQg4KFkOX*Bw%g^79JYI zLlvg0@X!Rz9mm4MVtAOsbQK!qZ}Sn!uENt3FlR9f&xqj} z3e#11Mgr!*XW^MKJX2x13eQZyoGL9mD~4w&OjqGq37Dg+g=fd`Y=!A6Ji7wS^O-pX zT=V(NT$9Z+*+(XuZ?Xj@``BcknCw%NeP*)HP45KM-DH25>`#;ZWwO6(WIQHe{n>>-Ctmn-bm7z0g+Hgl z!UtxNTX=2^&sCVN!gCWapCk*aZ(zl!0n6sD{2R|%M1fQ7$~;ja~@tMJzen5~9|zlq^*6sD{2 zHwl>ijD;7*@Ir;@D!ecOvuU#Mw=w*!!gLk>HUYCUv+#E@{GGyd75**(v;DL1_c8pv z!gLk>J^{1GwD1ox{DZ=D75*Usvyrv%k1_nC!gLk>F#)q%w(w6e{FB0T75*s!vn99i z&oTV7!gLk>IRUfpx9~49{ENbL75*gwbGWeZuQB|q!gLk>H34&ivG8v({F}mb75*&& zb4;@E?=k$l!gLk>JppqTv+y4={D;DH75*aubKtY^pE3NW!gLk>GXZm|wD4as{FlOX z75*y$b9A-v-!c5R!gLk>y8_JfnNscld_GglWVKCJ$7FR)wwTH4nXJCas!Z0vWQ&_@ z36m{pvZYM6w8@q+SwoX8YqI4`w!Fz!FxiSGTghaNOt!Mg8k=ktldWp9)l9a!$<{F0 znl&;Wld%5m!Y|dX)2U_WQsdh7ON~mU##L=fjcS#ewysqwm8y=aQ~W;pZ~M^IgDwSJf%Cs#B{3%p$jNtr)JQFkOXfC15^D7Oow`wH2nT zaP0)l$JN4hVz`dNbQP|XfVn1ExNZ#BRhX{AbrUcb9t$rP!;2|QSK-AHFjqGV*NfqL z3e#1%UIOM4YT^1ZTwh_j3fE7-T&FEu6~k2u(^a@C0dq63aDy0bpfFv98zf-vI2K+! zh8I_uuEL8aU~X#`ULuBBuEI+uU~bSBUMhx{Qkbs7OC?}- z0Ty05hL={DuEI+vV73|-UM7Z@QJAj6%Oqg-GZt%Rd~4s%=XX1%g6BY3e#11`2@@!)50sn@CpjkRd|I2%tqG2E5`7O z3e#11#RSZ5*}^Nu@Jb5PRd}TY%$D53jbgaiuNKxn|LID)3O7o??E5XeatyDmFkOXL zPQV;4EZjJT8!Jp#;l>G=6O4sdiQ!cgrmOHO37BJ&g;$N?RTZYI@Tv)zvzUcfi{aH2 zrmOI3377+)g;$T^)fJ|z@ahSeQ>BI1h~YI9rmOH837Dg+h1ZPXH5I0-@R}81p3gKX z;F|yb%vvUEYO=LWwvNfxHCZ#0t!J|JO}2r_HZ<8rCTni8jZL3FW+vO*WLubQ zOOtJ7vaL)z|-efzNY)6yrWU`%2wu{Mjt&#DVg!N|^ev^3NH_?SpR~LSh z3JV{YMQ-7>Vt6fu=_W-+{(!gLkhECI7=vhe0Hyt%@372Z4nvoo{s7BReq!gLkhA_24gv+$NN zyrsf)72Yxdv&XdXRx!Ml!gLkhDgm>RweZ$4ytTq~72Y}lvsEyraT&72Yuca~8AkPBFZb!gLkhDFJifv+&L_ytBe|72Y`kbE>rPE-}1| z!gLkhB>{7EweYSnysN@=72dT1%=4KR1zhv_OiPosGTCk>Yi+XKO}2;0_B7dECTnA| zy-l`{$@VqbekR-BWCxh6t;r5F*+C{d*kp&8>`;>(X0pRgc7(}}G}%!mJKAK&nCw`S z9cQxRYh*koVg1>K-y&Z4Ep*}2)rH@p!omk;kz2TB47XI6uEH%7FrOp~w~FCb3e#1% zRRZSYYT?~tcsGUVD!f|)=9*yP)-l{#VY&*pPQYAvEWCRR@2)Uig?CTDT-_|ZM-1jp2P2rmOJ2378wSh4+i${S>CF@O}xH zU4Vu6kKz3lrmOJ&37D;hg%60~0~DsK@Bs;!{fvd%#&BDO=_=ed0kdhc@PRRWpu%(& zJ}?2ZGqdnPF?^81bQL}*0ki$H@WC;Bu)=f|J~#oh$F%SvF?@)^bQL}%0ke^{@S!n$ zsKRs=J~RQdTek3FF?^W9bQL}<0kb8y@Zm9hxWaT5K0E=l@3-&~F?@u=bQL}#0du&p z@R2cmq{4I+J~9Dwg0b*XF?^K5bQL}-0dq{U@X;}Rw8C^1J~{z&7PIg%F?@`|bQL}( z0dwH9@Uby`tip5^J~jb!s0dsV<@bNKxyux%9KE49X^O+M0xaRYj zb|yQ~WG9*IWRtZw*(oMF)nuoctb@r;H`y5`>u9nwO?H;a&NkUOCOg+;=b7w$lU-o4 z3r%*B$u2h8B__MnWS5!ja+6(QvMWt?mC3HIk@1*>^=B9U3Gu=|K^Hz1urowa;J~IKc3$XB6F?^Q7bQL};0khSx@YykZw!(B3K05)k zpRw>cF?^1~bQL})0kdhc@VPO3uEKN`J~sigGqdn{F?^oFbQL}?0ki$H@cA)(zQS}B zK0g7o$F%STF?@l-bQQiJ0ke^{@P#pap~7?(zAyo^Tek2;F?^B2bQQiR0kb8y@WnBF zvBGo}zBmE1@3-(JF?@-_bQQiN0du&p@TD<)sls#>zBB=Ig0b*rF?^ZAbQQiV0dq{U z@Z~Xlxx#c6zB~bQ7PIgbF?@x>bQQiL0dwH9@Rc!qrNVR-zA^!Gs}HeQVzOIJ zcALp=H`yH~yVGQMne1+p-D9$QO?IEj?l;*3CVS9i51H&?lRaXxM@{ya$sRY^6DE7I zM#f_j)}LMYo#KVxNf$m{UHF|UEPP-VxrIB&aA$?-D%?2%^GUMsH8FgR!gLkBCIRzt zweYnue67NC6}~nBb4{@DbuoOM!gLkBE&+4lvGDaVe7(YS6}~e6zxI6}~wEbH}mpEirtH z!gLkBB>{6=v+%7ke5=BA6}~kAbMLh9Z83bC!gLkBEdg_bw(#vSe7nMQ6}~+IvkS2B z9Wi`|!gLkBBLTD3u<)HRe5b;66}~e8v!Ai>T`_!@!gLkBD*>}rP6EXaR!gLjWA^~%BweXWM{G`Hk6@Ib;%=4M23b^L;nJy-K z+GNj|>{*jNXR_x_*41P$n5>)0UNl*Ell3s!OD5}SvX@QvipgFzSud0IHrZTl%Vc`R_$SvF@ zhPx>zbDg&E3o-nH!gLjW zApvtUuyD5+?xrwZg}WtS?l=~HF@|4Mn6APvCSY!B7VaLy-4&**aQ6hvz0<-yVz`IG zbQSKAfVn|i_@x+rNnyGQzm$O41z5Oe4EI!+uEISNFk1}^zZ}CaD@<46mlH7i84JG> z!>=ezSK(I@FqURk&9IX8UL1-Z9);VY&+UPQdIj zE&N&xzosx2>mekTEQ;Ir_%G5oH=bQOL#0duOf@Ov@*p2Bn$elG!YbhYsN zG5o&5bQONT0?hN7eg$0f`AmP4ePFTyCi~E215Gx_WP?pM#AHKFHq2zhO*X=0BTY8S zWTQ<^L2WF95xPJ`ySD3EC{Sz>sBny8K!yhP2SK$v5FdtV74~XFb3e#11 zKmz8PVBrsA_(O&1D*Ryr=E7s)fiXN#VY&(rOu$^-EIcTN2PsTf;Xw(QOQ?kh$M9f< z=_))p0dt+U@Q@fDqA*>Bha_Nb1{NL~!$TFOtMJeS%pJ$V!(w=t!gLiLmVmjfS$KF1 z4_BD3!ow3V_f89sh~W_m(^Ysx0_Fy7;gK;sQenCZk4(Vq0xUc#hDRw(SK(0!n5~9| zN5}AJh3P6hIsvnvvGAA}9-}Z_g~uddHcb{D8^dE2rmOJS1kBFN!sB9ioWgV!9+!aG z{#kf@43AfsuEOIJFndf3Pl(|O3e#11LIP$ZYvGA8JW*k~3QtVH?3OJ&DTXI0OjqGa z379Rpg(t`GWQFM}JUIcg@3-)j7@ndqU4^G4U=9}+o*Khb6{f53)C9~4#=_HLc$&g= z6`q!WIVM?ndJIojn6ARp6EJ5n3(tt*84A-?ct!%|z-Qr^F+5XYx(d%sz?>>AJS&E0 zDNI-4SqYe@)U-ef_?ORWU`-4_KV4WHQ8?_``u)JnCwrJ z{bjPhYh*koVg1>KKPO)Jb9CX;)rCK&!omk;kz06f49``VuEKK@FrOp~&x_%C3e#11 zUIOOhYT=J!_#=hsD*RCb=9*yP`7u0SVY&*>PrzJwEW99w7br|u;ROkptDA*Cj^U3L zrmOJB37AW$g+GbmPZXxB@FxkF>$HVGjp0ufrmOI$37DIKg+Gho&lIMs@Mj5_JC21v zkKxZ1rmOJh37FfOg};d5FBGP$@D~Y~d#8oJjNva8rmOIm378wSg};j7uN0=M@K*_# zU4VtZj^VErrmOJR37D;hg};g6Zxp7h@HYvV{fvbd#_&Rg=_x#@O}3cH>Y1#*$*N4&z+{V?YzdPsX|knEwzSEXFCEo-vnOt!qqRxsI$ zCR@p5jZC(($r_t%6_c%Mveitsy2;is*_t&n9+R;C?7}bAsne-t=ThU^^-GOPrN&il zN{wojnzpW0DwV2^s#E;FQnA)+l{Wd;UsPZCr8@OG(QZ_`bWGD$buKAY6~AiQysB|k zol>g;1ZI(2xK<3;QkbrO;%X&eK1mj?9mBO1rmJx61kA_P!gXS}j>2>mu9JYdCRn&` z4A)hduEKQ_Fc%&RFBZd#DNI-4#S$=AHw)K`;d%J#ZqOE9Du$O*n6AQ0C17>|7G64rmsXgr!b>M$wi*^* zCWe<$n6ARhBw+S47H$~B4Hc%VaKi-5rpdy~#_+NV(^Yuc1kBFN!pp_*athN`c)0}3 z_Rqr0$MEtB(^Yu+1k4`O!Yjn^3JTLzc!dPaM%KbB#_);?(^YuI1k7&P!Yjq_N($3e zc%=l)mfXUPVz`mQbQNxtfZ6w3c;y&gSz)>gubhB6Tv)hq3^!JouELEIFeexbuM)$n zC`?!3RT41ABnz(^!>cMxSK(C?FlR9fuNK3rDNI-4)ecPySK-wYFsDij zuMxv*C`?!3H4-pKR|~Hh!)q!`SK&1)z&xL6QouF;{h75)*3@Kcn`|AEt!uJoCR@*B z>zix?lWl0SjZD_uWE-1o6O(Ofvdv7kxyiOL*_I~T%4A!cY#WnpYqIT3w!O)AFxieK z+sR}*n`{@8?OG$_F$wF>F8n6(!f&DrpRO+aCKVPwFpJ#6YsK(d3e#11tpv;`$-+%z zxT(T)6>ge<`M6qm?HFELVY&*hoq)L}Sa_WnUPocN3a^uZx$szc-56e1VY&*hn}E5x zS-4pYH&d9d!p#yemrx6@7sKl*OjqIc5-`_k3$Gu;>nluG;q?1lI6vG=SOjqHJ5-|5p3pbD9<_gnQxOoER25sSuV|ZhQ z=_Xl2ZiY>yh8%!m}KD{V|Yh}=_;RLsHQ9kC zJIG`Qo9qyi9cr?}Om?`*jxgDgCOgVxN1N;zlO1ca<4ksZjf}@6tUtT(Tf_^$g)V%$ zy6{_6Sopv!atpVN;g$;1Rk&pW=96UMRx#X4VY&*pO2B+vExcO{@1`(ag?CH9ToWwZ zI)+;-OjqI73789yg?EqP-4&**@a_qitDA-Qh~YgHrmOHC37AW$h4+l%Jr$;_@SX{n z>$HXUis8K!rmOH?37DIKh17=`I7d`tr7z-QrOWB6Ew=_-6| z0_Id{;p1ZXIECpdd|U$N=xX8PWB7Q5=_-7D1(@eEClqka=PvC`cB08nGTF%{Yj3hs zOm?ctPBU2tlbvp|GfdXeWM`V}ER&sWvU5y!uF1|b+4&~Bz+@Mi>>`t0Y_dyCcB#oO zGuh=PyTW8wn(Qi*U0oyNF$wF>F8mYXg@1xBe7d^uPpGi)fm!4hZWqJt6sD_iy9CT9 z$-*bb@QDi3RrtgN%*WNjC&lnd3e#2iqy)@0!NMoU@W~3(Rrurt%!S9o?PIvT!gLjG zpMbf#S@@I~K1E@=3ZIgIxrAEy)EGWhVY&*Rnt-`ZTllmXK22e|3ZIsMxfxivLkxFN zn6APd5-@ih3!fgtrz=cX;nNc^w>1l&5yNLFOjqGE5-|5p3wMm+jtbLNxMKq525sRp zWB5#k=_-6?0%jLr;j?1+EQRSRd{zQxt6|}@WB6=^=_-770%kvB;d5g69EIsBd`<#p z(`4auWB6Q!=_-6~0%m7s;qzknJca2hd|m=(`)A?vWB7c9=_-7F0%nhC;R|B;0)^=+ zd_e+cBWvLcWB5Xa=_-6-0%o^t;frGUB8BNHd{F{sOK#zdWB6i)=_-720%qTD;Y(up z5{2n1d`SZ4aADy~WB5{q=_-6_0_Fr`;mcz9GKJ|Xd|3kKm}KG0WB77~=_-7A0_H4c z;VWYJ3Wez^d_@B0z-QqrWB5vi=_-6>0_Id{;j3c!DuwAPd{qMG=xX7sWB6)?=_-76 z1(@eEoeH?-^O?>jyT)YKn(R80U2n1*Om?HmZZg@;CcDLCx0>uWlihBzJ4|+`$?h`Q z-6p%oWcQlvK9k*VvIk7|pvfLG*~2D##AJ_}>@kx)Zn7s#_GFEW$0V#jyYM^33%`>t ze7d^uJ5^Ztz$|hLcaGuC3e#1%a{}g*WZ`RK_!@=jDtt`>=HqJNYh(CYh3P7MZ35<+ zVBzax_&SB@Dtui6=E7s)>tpzOh3P7MeFEm{X5kxR_y&dPDtto%<`Qb*8)NuJh3P7M zV*=(nZQ+|@_$GzvDtuD{=4N2wn`8K9h3P7Ma{}g$W8qt3_!fofDtt=<=C)?xTVwcE zh3P7MYXaupY2n*q_%?;-sh3P7MZvtkIY2o`~_&$Z{Dtuo8W+Q9i`(yZih3P7Me*$K=Y~crD_yL9K zD*QkKW=n422V?j_h3P8%U;<{}Z{dex_#uVqD*R9a=5S%*hhz9*h3P8%a02E8W8p_) z_z{KaD*Q+S=9pyRM`QR=h3P8%XaeReX5q(T_%Vg)D*RXi=D=s+$7A?$h3P8%cmn2B zY2hbg_z8vSD*QwO=ICnSCu8_Yh3P8%WCfV#Gfx$8&F3>+O!lny=t;vChKjo*G%@h$@-Y=4U@fTvbRk3w#oXM z>>ZQ6YqIxD_I{0w$0V#jyYQci7yeVa@agKpf2zX52WF95xJwLoQJAj6T@o;#Bnv+s z!%r(rSK+4!_O;BSK;RqFqcpZca7n$3e#1%YXasvZQ&PU_yvXOD*QqM=4N2wZZX_VVY&); zOTgT5Ec{{&zo;->g;VgSPNXG5nIk zbQOLn0kaFRaL*X-sW4rIdnRDE8Ww&zhF?~guEH-TVD>W>ekF!qQJAj6uOwhLO%{GN zhF?{fuEMV-V0LB}?iItm6sD_iuLR8Y&%(W9xVOS|74DsY*<)JxwHSU)VY&*xmVnvF zTKM%CeqCX@3csF!*)3bRPYm}_n6AQo5-?kG3%?P=ZzxPx;WrX6`+f_*8N+WXOjqGI z6EKGh3%?b^Zz)Vy;kObnCm0LA9m8)cOjqHz6EMdl3-^uTz6#S-xNidHEN0<%V)z|} z=_>qA0_MPH;df*BU4`i?{B8o~RB7S&V)#9U=_>qQ0_Nyy;rC>$Yn{0^5hMH`c$%dP3gvmylY?R4Hn{159#+q!L$;O*( zg2^VDY?8?)n{0~7rkZS;$)=lZhRJ4{Y?jGp*T{HG!uqodzhAuY`{}}`s|&whg@q5y zBDZk=81AnyU4{E6U_MC}{vd`wP?)a5A0%Ktt`;5;!vhqitMGsX%r(KnAI9*93e#2i z!vxHQ$HD_+c%Z^`6&{#?xw=_+Pz(=Jn6AQu5-^ug3lEOr!3xt=cyI#dI&I-0F+4N8!xI#ytMG&b%tqG26JvOy!gLj$n1IKKq546Mtdm=of~reqrWR z_`(45MX>PP7@n&zU4`c+V7}58o)^RO6sD{2yadc2r-eU?;g1xitMEq&m^EYJ`7u0S zVY&*>Prxip3onS_1q#zuctHYY^;`Jk82(scx(a`sfcc17_>&m^L}9uLe^LSdvedS; zOsV$5y^8-f`j}GH!m8qD{yXWVtx82+XW?eWFPACRUAX?DyureTyjJ5|?b70lejmSQ zvEsE|YA;vnv#8Tz(T|rb`thk!-Qqu-ggmEK>S~+bZS+j;`^f3q91CPYW-b& zJso$a>OM9$Z{JdX>lg>N){IgHFpvmr+oYcN!lkKgudw(r=neR1_Dm0EQ; z<&^FIRsWy;SggN)q5rerUQEvg@;~2#bv@_$C%^mkfYTawzq_im(R#h7a`C*P_|D+G z&fpD-?=6b&T-f$4zAsUHKce`)Wbysv;`>s?_j8Kx2%r5~Dy>oc%;#i_ZMIwV`$}DR zs=adYVv8dC_qP`R{zdUKzwX@ofZzW7WeVhfeEZ+cU%X|h_Qa7BAKPTrhW9>L%-{Ui zo~?^)u^QPiMYeMBoj2z@Hw1n!v;Ob$;^qJPc2Mc;;{PrvH7V_0I;;5kywaur^;+f^ zD-~Cae}6m{`8C#%dynn7$S`E_?eIcBuXIM~#6@$Q@vrc|@#AvE8}U~BnJ`D@x6ZYIAzhF3CRVe4#ls}3PxD}AD=(ctk2i0$A9^) u3*n?iGqhi{0{?sSZ1+F-`{(n2!9V{0ji3IzUw`he|2N!!zsLWH|Gxl$PuL*< literal 0 HcmV?d00001 diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/layout/script/export-valid-layout-with-3-tabs.xls b/dspace-api/src/test/data/dspaceFolder/assetstore/layout/script/export-valid-layout-with-3-tabs.xls new file mode 100644 index 0000000000000000000000000000000000000000..f8b2c0692f503db50ba56cec2fb3341800e8e5aa GIT binary patch literal 20992 zcmeHPYiwM{b)J2ZyL@OV$t%lt?5<>6mTi%-l+>2vIO39AOHBC+xs0VWF11`WnKNhRUhd!j-ToK9{+-)@pd!~}DxqHA%BU`f zAH)4ILk}o*5AMl(eQRsWuyOm&^(NB5Yd+Ql1~7?~LgJWrBc+itNIgisNPS5CNc)ho zNH77lAL#(nL8M!da!7}eZbdqbbQ{v`NOvH;3+YazyO4ecDUWmn=_u0ONVFr9?(^w4 zkt*MmP-j&g|5_M}qFTjwLtTaDcYhs%#kd{cO-UwMO4CTCP>{|6kXL#!!wC!*wh&kH-#Rt%+k* zO2Xp0;Gfd)Od*A&()*dXTei$1@Ii#cf?a_z2Um--#_PYu7Qz4Ei_zqj(JYp&~g^4U$z|KaZV)M2~wJMr0S zyYg>c{r8qp!hT7uyz(zuU@89Us-v>65`upHUxBk9m zBeiu=&*W=}Q$EcQ1%9GcUc81d`AM3ky%++iQ$L%woCe>8cFnvg-L*8cC_r7h>u^)K?hPZGMq0 ztFF!S{JKYiKL&9O1c``=6Q17;8u?9BR5OD|(hMN^%@u{;v>Se#Hr}IpHyJcOhn%$f zYz+MLbvS929#(g~!mtu!mF9p79#Z`YLjlFxb#P?q(YGl;u{nzS;yiabiLU z^l@bRd~ptO_L}a)px=YRX6tIrLzvz0nqIk4x#Tt0q(>+6O|P=v@Rp#0)$(PpQf@3k zzkYS}O{!wxP1X8(Yi+&Ngy5FfY)Lly4TGu;o)i)t!%7wfg9e5GC+d@q_{`_)#>!-m>s9m4Rl-Yl6GR=`>VcY_=NZ zN{c$F)>j(kwM$olxmI6YX?a&#Xuz1l+1cs&8EhTv*b2Q_gh zKtMyX@C|t~W|+M$S1+qHf$8&u6f}pG!HoN0FvnmQ+67cwSJBN&UU{jyx`Ocvc~Qul zo*f+@TbR2rHM%fYER`@j=b$;L|6>>{@zfF5i$Ru3h4~Uzlktye|1^g&Vyz*qM@$W; z;SbFh=Zk0+3%7@2)g_E#b-C&_FeXT z2<^W5u)Ho|DdzrUzg}twut@&+8?XGs5NvIRC6S3a!UBv$v z&zSF+nn&Adbe-Ks@YcgKA}-l|y4J>krQ;~MaoExfbN(x1=_Epm%e|IPA+*?k#M01h zY$GkSit{|mght3w={Uv=^BN62>l!@{%j3M4@`UFh0sFYAK=#=dV6$izZ zgi3&78$u;PafF0Qfx=|b6zk_8$xv()#_i{^P1+6XQRg`wJ_*G(X+J`-O-wvZ5ru7& zHk8xj!IIQ^PM_cj#Wtxsq1-m(z?fq=sPovDtUm2y$%Jmx;7sT?8T?6}i<5ixoZgR1 z9^2$O;mc}<_Pu!y_pt3v_EHZN)Ra|;&4)l|RdVyV=4Uo#G(k95T=nRBR4w)Sf%pOS zJ)J+SKd97&J6lSp*S;` zD-^Amr`ST#iV?l|6s?#i+sxZ(27emforY#-qRe1!q@*5O;kh`eo#rfCkvU7<#o}F{ z60l^c!xpe8Yl<_}%90sMFU%G=L$kX7tjF=p>63V&Qpn?(JSl3@&U0=^#UHViq;7>c zq$FG6IlfTT5Kr`lVw=W4a3pM-sZg6Kv}sYNG&0j>3KDIbw6dJaqb=8WMQ-0rma1@-_lgx@7-l-p5t!pvvfDCW+Q89 z9u6EoXlVqA+g5$rnH(&bRX@{a)sONxoBbk2I_0LIACVmq?Yc$UJ zX(gvZl2d_X+P;~7loqYkyUi^!1*r1z(K;_?N3`2FF4>IL;kK=mP-x6yq+C>!1HLu`LK zD9NWQe5gaz?N(IA-+zu3%EtQQ#S?xF_UwH@efI3JfMU=1Y)IN>&mIpb_QiPb81Ay_ z13_LdD4W*@iVu^dm3~me0kscQ!KbJvyTZu1fI)Bf7$Q~B$Mu?IMnX;mMuOI;`_X`M z@x~DoWSv#pMaZZ17E`;mn(Hw&Dr+@u-qonA(ng~R_ztVlgtirkS-Xu6`Pk1QOolKeLzt47 zjafD3>plsDx8dC#u>h{9S@ncZ(TmtIm%W$4YxiF6n9Huq;FW0LY|t9V{9Hh}cvGP< zPlfQ>F{F)3d|*&Wd=S+d;~MJ&cz%;p%poqdo8=NUW7v#Bi*cHM=ziBno zjrp5X22(eDis;0(yzUUDZjEWem&A8ba%wW5T+MWATbl~<+}Yb5!jukS;^SY7DXlSa z9da?HHKu95?X<=;6HvtTp@4ERr8TD6AkW2=4q@WDVfBOu)1VlubB1O#COf)f@tKgG zG8)sIFDaw-R0=2;Qzm5bnE(Z&+>EwyS`TMdE;xy#PnHu3;^AIGu(~?p$%VBg#>uw_ zGq6YFnfLMZgl1q*2v3j3^I^YaZwN(i2t}`h!uo;U5Q<){l_!JNI8x^W%GFA52nA!C ztTHILE?ZsjjNR&j&k(lTa9_w?`ZS6QzT7^oi;o19i=t24%g+aSuD$eyFflH$nEFGQ z`ZXq=@3`aBuQ3^q-mfEl$cUrV=XA!) z>f$!E`awwtXbs*trRdh9Xc1xTIod>rx+O|pP{$5vabmjXXpk(~jy*@))gfA<kj+4$%@NFGe`+(IVQ}bF@7jq9saRTrt|CMJ%-EXnQ+EOO(7AC$&e5 zm}$?^_H~GsD0wlSYL6DN)t;m6?+`6f@?xab9xbA-Jx9B*L$pN6i#wP0X!pGVXtN!n zm2*DEbUUHVz5!?%eRkq+<+&Ko4tGL3@DrjnC*w&(%e=1W&(J#+4ACi`$E@k}+v;RI z{I=7!GjZpg2%fLGXWD%7^!?l9td#Fy@ClEbub+cDXWBdich9VO2L3+hZ7TTNZD-aJ z>Ch9KGwYrtKHH|C~2)lG-aYBM)DtDW&%k+WJOUEa%B2=XZJVn9(|IiS*@76U5d zP(*0YN#u0Z$lLzBGl%z|q(|~hvl390w-iv6=LHnyE&CM5#O|}@eGjvfZ}*hb=COY( zK`X92KFPHGa_-5Q^e0W*s zrl0Yl4rhY#WZd`jYU9ah2=rw71;@AZ8y?#{ne()cwtVa)pBu<~o%Cd`=i+&{`!tWw zjNN>CGWTg6J(>HojwgJsr{npT`*aS`&o)oTxhK(^@pOG{JYB-^bUS&ghtQ7kR(Ig< z{*?L)?bmwXSq=jU^BBe0nLuO7s=Qy1p5-?6i=bQ^zUbQU;SOvVb#`sTREF5_PxW)^ zWqtjte)rhiABSzo^^)`M@k?jI?;gi@_3rVK^X@R!@$b%RN*lgA^6qiVDGy(_&Bo{& zoGjR!mEb@^-T9XgSWCD32o?3)YAdQ zl8nsRioBojZl9vgbNZJ_n8y__t3DCru}23%ao%$zp7lrJ;yyAcn;JFJcmBwABMcPdlGK6xJELpzTsc2=tge( zg96NN?r`LYj5PJg?{}`ZwgzGt<`X!dtPCUZ{AvP;p7A^q*P10Ho?z3tp45K-`WcdKZ*SR1$D=`zW@LL literal 0 HcmV?d00001 diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index 56055504ad48..d057647a792a 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -124,7 +124,12 @@ - + + + + + + diff --git a/dspace-api/src/test/java/org/dspace/layout/script/ExportCrisLayoutToolScriptIT.java b/dspace-api/src/test/java/org/dspace/layout/script/ExportCrisLayoutToolScriptIT.java new file mode 100644 index 000000000000..2d09eb48070b --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/layout/script/ExportCrisLayoutToolScriptIT.java @@ -0,0 +1,191 @@ +/** + * 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.layout.script; + +import static org.dspace.app.launcher.ScriptLauncher.handleScript; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METADATA_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METRICS_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX_POLICY_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.METADATAGROUPS_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB2BOX_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB_POLICY_SHEET; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.TAB_SHEET; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.EntityTypeBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.content.EntityType; +import org.dspace.layout.CrisLayoutTab; +import org.dspace.layout.factory.CrisLayoutServiceFactory; +import org.dspace.layout.service.CrisLayoutTabService; +import org.dspace.util.WorkbookUtils; +import org.junit.After; +import org.junit.Test; + +/** + * Integration tests for {@link ExportCrisLayoutToolScript}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + * + */ +public class ExportCrisLayoutToolScriptIT extends AbstractIntegrationTestWithDatabase { + + private static final String BASE_XLS_DIR_PATH = "./target/testing/dspace/assetstore/layout/script"; + + private CrisLayoutTabService tabService = CrisLayoutServiceFactory.getInstance().getTabService(); + + @After + public void after() throws SQLException, AuthorizeException { + context.turnOffAuthorisationSystem(); + List tabs = tabService.findAll(context); + for (CrisLayoutTab tab : tabs) { + tabService.delete(context, tab); + } + context.restoreAuthSystemState(); + } + + @Test + public void testWithValidLayout() throws Exception { + context.turnOffAuthorisationSystem(); + createEntityType("Publication"); + createEntityType("Person"); + GroupBuilder.createGroup(context) + .withName("Researchers") + .build(); + context.restoreAuthSystemState(); + + String fileLocation = getXlsFilePath("export-valid-layout-with-3-tabs.xls"); + String[] args = new String[] { "cris-layout-tool", "-f", fileLocation }; + TestDSpaceRunnableHandler handler = new TestDSpaceRunnableHandler(); + + handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl, eperson); + assertThat(handler.getErrorMessages(), empty()); + assertThat(handler.getWarningMessages(), empty()); + + List infoMessages = handler.getInfoMessages(); + assertThat(infoMessages, hasSize(6)); + assertThat(infoMessages.get(0), containsString("The given workbook is valid. Proceed with the import")); + assertThat(infoMessages.get(1), containsString("The workbook has been parsed correctly, " + + "found 3 tabs to import")); + assertThat(infoMessages.get(2), containsString("Proceed with the clearing of the previous layout")); + assertThat(infoMessages.get(3), containsString("Found 0 tabs to delete")); + assertThat(infoMessages.get(4), containsString("The previous layout has been deleted, " + + "proceed with the import of the new configuration")); + assertThat(infoMessages.get(5), containsString("Import completed successfully")); + + assertThat(tabService.findAll(context), hasSize(3)); + + args = new String[] { "export-cris-layout-tool"}; + handleScript(args, ScriptLauncher.getConfig(kernelImpl), handler, kernelImpl, eperson); + + File importedFile = new File(fileLocation); + File exportedFile = new File("cris-layout-tool-exported.xls"); + exportedFile.deleteOnExit(); + + try (FileInputStream fisImported = new FileInputStream(importedFile); + FileInputStream fisExported = new FileInputStream(exportedFile)) { + Workbook importedWorkbook = WorkbookFactory.create(fisImported); + Workbook exportedWorkbook = WorkbookFactory.create(fisExported); + + Sheet importedTab = importedWorkbook.getSheet(TAB_SHEET); + Sheet exportedTab = exportedWorkbook.getSheet(TAB_SHEET); + + assertEqualsSheets(importedTab, exportedTab, 6); + + Sheet importedTab2Box = importedWorkbook.getSheet(TAB2BOX_SHEET); + Sheet exportedTab2Box = exportedWorkbook.getSheet(TAB2BOX_SHEET); + + assertEqualsSheets(importedTab2Box, exportedTab2Box, 6); + + Sheet importedBox = importedWorkbook.getSheet(BOX_SHEET); + Sheet exportedBOX = exportedWorkbook.getSheet(BOX_SHEET); + + assertEqualsSheets(importedBox, exportedBOX, 9); + + Sheet importedBox2Metadata = importedWorkbook.getSheet(BOX2METADATA_SHEET); + Sheet exportedBOX2Metadata = exportedWorkbook.getSheet(BOX2METADATA_SHEET); + + assertEqualsSheets(importedBox2Metadata, exportedBOX2Metadata, 16); + + + Sheet importedMetadataGroups = importedWorkbook.getSheet(METADATAGROUPS_SHEET); + Sheet exportedMetadataGroups = exportedWorkbook.getSheet(METADATAGROUPS_SHEET); + + assertEqualsSheets(importedMetadataGroups, exportedMetadataGroups, 10); + + + Sheet importedBox2Metrics = importedWorkbook.getSheet(BOX2METRICS_SHEET); + Sheet exportedBox2Metrics = exportedWorkbook.getSheet(BOX2METRICS_SHEET); + + assertEqualsSheets(importedBox2Metrics, exportedBox2Metrics, 3); + + + Sheet importedTabPolicy = importedWorkbook.getSheet(TAB_POLICY_SHEET); + Sheet exportedTabPolicy = exportedWorkbook.getSheet(TAB_POLICY_SHEET); + + assertEqualsSheets(importedTabPolicy, exportedTabPolicy, 4); + + Sheet importedBoxPolicy = importedWorkbook.getSheet(BOX_POLICY_SHEET); + Sheet exportedBOXPolicy = exportedWorkbook.getSheet(BOX_POLICY_SHEET); + + assertEqualsSheets(importedBoxPolicy, exportedBOXPolicy, 4); + } + + } + + private EntityType createEntityType(String entityType) { + return EntityTypeBuilder.createEntityTypeBuilder(context, entityType).build(); + } + + private String getXlsFilePath(String name) { + return new File(BASE_XLS_DIR_PATH, name).getAbsolutePath(); + } + + private void assertEqualsSheets(Sheet sheet1, Sheet sheet2, int cellsCount) { + assertEquals( + WorkbookUtils + .getRows(sheet1) + .map(row -> + convertToString(WorkbookUtils.getRowValues(row, cellsCount)) + ) + .sorted() + .collect(Collectors.toList()), + WorkbookUtils + .getRows(sheet2) + .map(row -> + convertToString(WorkbookUtils.getRowValues(row, cellsCount)) + ) + .sorted() + .collect(Collectors.toList()) + ); + } + + private String convertToString(List list) { + StringBuilder value = new StringBuilder(); + list.forEach(s -> value.append(s)); + return String.valueOf(value); + } +} diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 21e3a8f19802..9289ec01e8c3 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -176,6 +176,7 @@ + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index cf31dd3c636d..cae340f0e690 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -123,7 +123,12 @@ - + + + + + + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml index 9842e3b9390d..89a5757c0ce5 100644 --- a/dspace/config/spring/rest/scripts.xml +++ b/dspace/config/spring/rest/scripts.xml @@ -88,7 +88,12 @@ - + + + + + + From fbb140577658cec44881c428466c035312c611ca Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 28 Nov 2022 10:54:54 +0100 Subject: [PATCH 065/394] [8563][DURACOM-101] Fixed regex validation Fixed: - Checks for string emptiness while validating input; Feat: - Saved Compiled pattern using ECMAScript standard inside DCInput. --- .../java/org/dspace/app/util/DCInput.java | 38 +++++++++++-------- .../dspace/app/util/RegexPatternUtils.java | 9 ++++- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index eb5a51ace279..11f9aadd869b 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -132,10 +132,15 @@ public class DCInput { private boolean closedVocabulary = false; /** - * the regex to comply with, null if nothing + * the regex in ECMAScript standard format, usable also by rests. */ private String regex = null; + /** + * the computed pattern, null if nothing + */ + private Pattern pattern = null; + /** * allowed document types */ @@ -179,7 +184,7 @@ public DCInput(Map fieldMap, Map> listMap) //check if the input have a language tag language = Boolean.valueOf(fieldMap.get("language")); - valueLanguageList = new ArrayList(); + valueLanguageList = new ArrayList<>(); if (language) { String languageNameTmp = fieldMap.get("value-pairs-name"); if (StringUtils.isBlank(languageNameTmp)) { @@ -208,13 +213,13 @@ public DCInput(Map fieldMap, Map> listMap) visibility = fieldMap.get("visibility"); readOnly = fieldMap.get("readonly"); vocabulary = fieldMap.get("vocabulary"); - regex = extractRegexField(fieldMap); + this.initRegex(fieldMap.get("regex")); String closedVocabularyStr = fieldMap.get("closedVocabulary"); closedVocabulary = "true".equalsIgnoreCase(closedVocabularyStr) || "yes".equalsIgnoreCase(closedVocabularyStr); // parsing of the element (using the colon as split separator) - typeBind = new ArrayList<>(); + typeBind = new ArrayList(); String typeBindDef = fieldMap.get("type-bind"); if (typeBindDef != null && typeBindDef.trim().length() > 0) { String[] types = typeBindDef.split(","); @@ -239,19 +244,20 @@ public DCInput(Map fieldMap, Map> listMap) } - private String extractRegexField(Map fieldMap) { - String regex = fieldMap.get("regex"); - Pattern generatedPattern = null; + protected void initRegex(String regex) { + this.regex = null; + this.pattern = null; if (regex != null) { try { - generatedPattern = RegexPatternUtils.computePattern(regex); + Optional.ofNullable(RegexPatternUtils.computePattern(regex)) + .ifPresent(pattern -> { + this.pattern = pattern; + this.regex = regex; + }); } catch (PatternSyntaxException e) { log.warn("The regex field of input {} with value {} is invalid!", this.label, regex); } } - return Optional.ofNullable(generatedPattern) - .map(pattern -> regex) - .orElse(null); } /** @@ -528,8 +534,12 @@ public String getScope() { return visibility; } + public Pattern getPattern() { + return this.pattern; + } + public String getRegex() { - return regex; + return this.regex; } public String getFieldName() { @@ -562,8 +572,7 @@ public boolean isQualdropValue() { public boolean validate(String value) { if (StringUtils.isNotBlank(value)) { try { - if (StringUtils.isNotBlank(regex)) { - Pattern pattern = RegexPatternUtils.computePattern(regex); + if (this.pattern != null) { if (!pattern.matcher(value).matches()) { return false; } @@ -573,7 +582,6 @@ public boolean validate(String value) { } } - return true; } diff --git a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java index a31aee805d2f..63657aa25daa 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java @@ -35,14 +35,19 @@ public class RegexPatternUtils { /** * Computes a pattern starting from a regex definition with flags that * uses the standard format: /{regex}/{flags}. - * If it's a valid regex a non-null {@code Pattern} will be retrieved, - * a {@link PatternSyntaxException} exception will be thrown otherwise. + *
+ * If regex is null, empty or blank a null {@code Pattern} will be retrieved + * If it's a valid regex, then a non-null {@code Pattern} will be retrieved, + * an exception will be thrown otherwise. * * @param regex with format /{regex}/{flags} * @return {@code Pattern} regex pattern instance * @throws PatternSyntaxException */ public static final Pattern computePattern(String regex) throws PatternSyntaxException { + if (StringUtils.isBlank(regex)) { + return null; + } Matcher inputMatcher = PATTERN_REGEX_INPUT_VALIDATOR.matcher(regex); Pattern pattern = null; if (inputMatcher.matches()) { From 4a7952af155c04a174f3b323f1a23e4dd507b88d Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Thu, 1 Dec 2022 08:45:57 +0100 Subject: [PATCH 066/394] 97183 Added SubmitFeature: checks if eperson has ADD rights to any or specific collection --- .../authorization/AuthorizationFeature.java | 5 +- .../authorization/impl/SubmitFeature.java | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeature.java index f2f1ae31f530..353e2b9957b2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeature.java @@ -13,6 +13,7 @@ import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.SiteRest; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.springframework.core.annotation.AnnotationUtils; /** @@ -33,7 +34,7 @@ public interface AuthorizationFeature { * wide feature * @return true if the user associated with the context has access to the feature for the specified object */ - boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException; + boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException; /** * Return the name of the feature @@ -69,4 +70,4 @@ default String getDescription() { * @return the supported object type, required to be not null */ String[] getSupportedTypes(); -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java new file mode 100644 index 000000000000..dce3ead8bea2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java @@ -0,0 +1,58 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization.impl; + +import java.sql.SQLException; + +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@AuthorizationFeatureDocumentation(name = SubmitFeature.NAME, + description = "It can be used to verify if a user has rights to submit anything.") +public class SubmitFeature implements AuthorizationFeature { + public static final String NAME = "canSubmit"; + + @Autowired + AuthorizeService authService; + + @Autowired + CollectionService collectionService; + + @Autowired + Utils utils; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException { + if (object == null) { + // Check whether the user has permission to add to any collection + return collectionService.countCollectionsWithSubmit("", context, null) > 0; + } else if (object instanceof CollectionRest) { + // Check whether the user has permission to add to the given collection + Collection collection = (Collection) utils.getDSpaceAPIObjectFromRest(context, object); + return authService.authorizeActionBoolean(context, collection, Constants.ADD); + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[0]; + } +} From a6992efd45d72a3cc1ee515ad7af88047d8ddf70 Mon Sep 17 00:00:00 2001 From: Samuel Date: Tue, 7 Dec 2021 11:21:21 +0100 Subject: [PATCH 067/394] taskid 85555 Custom DSpaceControlledVocabulary for WB syntax --- .../WbDSpaceControlledVocabulary.java | 30 ++++ .../test/data/dspaceFolder/config/local.cfg | 2 +- .../DSpaceControlledVocabularyTest.java | 2 +- ...aryEntryDetailsChildrenLinkRepository.java | 2 +- ...ularyEntryDetailsParentLinkRepository.java | 2 +- .../VocabularyEntryDetailsRestRepository.java | 4 +- .../app/rest/WbVocabularyEntryDetailsIT.java | 52 +++++++ .../app/rest/VocabularyEntryDetailsIT.java | 136 +++++++++--------- .../app/rest/VocabularyRestRepositoryIT.java | 4 +- dspace/config/dspace.cfg | 2 +- 10 files changed, 159 insertions(+), 77 deletions(-) create mode 100644 dspace-api/src/main/java/com/atmire/dspace/content/authority/WbDSpaceControlledVocabulary.java create mode 100644 dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java diff --git a/dspace-api/src/main/java/com/atmire/dspace/content/authority/WbDSpaceControlledVocabulary.java b/dspace-api/src/main/java/com/atmire/dspace/content/authority/WbDSpaceControlledVocabulary.java new file mode 100644 index 000000000000..ed05325ace20 --- /dev/null +++ b/dspace-api/src/main/java/com/atmire/dspace/content/authority/WbDSpaceControlledVocabulary.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 com.atmire.dspace.content.authority; + +import org.dspace.content.authority.DSpaceControlledVocabulary; +import org.w3c.dom.Node; + +public class WbDSpaceControlledVocabulary extends DSpaceControlledVocabulary { + + @Override + protected void init() { + super.init(); + hierarchyDelimiter = " " + hierarchyDelimiter + " "; + } + + protected String buildString(Node node) { + if (node.getNodeType() == Node.DOCUMENT_NODE || ( + node.getParentNode() != null && + node.getParentNode().getNodeType() == Node.DOCUMENT_NODE)) { + return (""); + } else { + return super.buildString(node); + } + } +} diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 3c19a68e9fd1..74e3e14d6a8b 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -97,7 +97,7 @@ event.dispatcher.exclude-discovery.consumers = versioning, eperson # (This overrides default, commented out settings in dspace.cfg) plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ org.dspace.content.authority.DCInputAuthority, \ - org.dspace.content.authority.DSpaceControlledVocabulary + com.atmire.dspace.content.authority.WbDSpaceControlledVocabulary # Configure some more Plugins for PluginTest class # NOTE: Plugins are just *interfaces*. So, here we are defining some plugins diff --git a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java index 7ade9c582dc4..255b070e5eac 100644 --- a/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java +++ b/dspace-api/src/test/java/org/dspace/content/authority/DSpaceControlledVocabularyTest.java @@ -86,7 +86,7 @@ public void testGetMatches() throws IOException, ClassNotFoundException { CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), "farm"); assertNotNull(instance); Choices result = instance.getMatches(text, start, limit, locale); - assertEquals("the farm::north 40", result.values[0].value); + assertEquals("north 40", result.values[0].value); } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java index 044710d25b61..1788fa893b36 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsChildrenLinkRepository.java @@ -45,7 +45,7 @@ public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpace @Autowired private AuthorityUtils authorityUtils; - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") public Page getChildren(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java index 379928d9cc07..d7a1ff85c363 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsParentLinkRepository.java @@ -40,7 +40,7 @@ public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRe @Autowired private AuthorityUtils authorityUtils; - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") public VocabularyEntryDetailsRest getParent(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java index c206721361e0..911a4ae49bdf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -60,13 +60,13 @@ public void afterPropertiesSet() throws Exception { VocabularyEntryDetailsRest.PLURAL_NAME + "-search"))); } - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") @Override public Page findAll(Context context, Pageable pageable) { throw new RepositoryMethodNotImplementedException(ResourcePolicyRest.NAME, "findAll"); } - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") @Override public VocabularyEntryDetailsRest findOne(Context context, String name) { String[] parts = StringUtils.split(name, ":", 2); diff --git a/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java new file mode 100644 index 000000000000..cd7baae91586 --- /dev/null +++ b/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java @@ -0,0 +1,52 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package com.atmire.dspace.app.rest; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.everyItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.junit.Test; + +public class WbVocabularyEntryDetailsIT extends AbstractControllerIntegrationTest { + + @Test + public void testCustomWbSyntax() throws Exception { + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token) + .perform(get("/api/submission/vocabularyEntryDetails/topics:Information_Technology")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.value", is( + "Information and Communication Technologies :: Information Technology" + ))); + + getClient(token) + .perform(get("/api/submission/vocabularies/topics/entries") + .param("filter", "Information and Communication Technologies :: Information Technology") + .param("exact", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.entries[0].value", is( + "Information and Communication Technologies :: Information Technology" + ))); + + getClient(token) + .perform(get("/api/submission/vocabularyEntryDetails/search/top?vocabulary=topics")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", everyItem( + hasJsonPath("$.value", not(startsWith("Topics:"))) + ))); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 4962e1aef282..a580c28c92a6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -57,12 +57,12 @@ public void findOneTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$", VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB110", "Religion/Theology", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))) + "HUMANITIES and RELIGION :: Religion/Theology"))) .andExpect(jsonPath("$.selectable", is(true))) .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) .andExpect(jsonPath("$.otherInformation.parent", - is("Research Subject Categories::HUMANITIES and RELIGION"))) + is("HUMANITIES and RELIGION"))) .andExpect(jsonPath("$._links.parent.href", endsWith("api/submission/vocabularyEntryDetails/srsc:SCB110/parent"))) .andExpect(jsonPath("$._links.children.href", @@ -73,7 +73,7 @@ public void findOneTest() throws Exception { public void findOneUnauthorizedTest() throws Exception { String idAuthority = "srsc:SCB110"; getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) - .andExpect(status().isUnauthorized()); + .andExpect(status().isOk()); } @Test @@ -102,30 +102,30 @@ public void srscSearchTopTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"), + "HUMANITIES and RELIGION"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", - "Research Subject Categories::LAW/JURISPRUDENCE"), + "LAW/JURISPRUDENCE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", - "Research Subject Categories::SOCIAL SCIENCES"), + "SOCIAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", - "Research Subject Categories::MATHEMATICS"), + "MATHEMATICS"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", - "Research Subject Categories::NATURAL SCIENCES"), + "NATURAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", - "Research Subject Categories::TECHNOLOGY"), + "TECHNOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", - "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", - "Research Subject Categories::MEDICINE"), + "MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", - "Research Subject Categories::ODONTOLOGY"), + "ODONTOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", - "Research Subject Categories::PHARMACY"), + "PHARMACY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", - "Research Subject Categories::VETERINARY MEDICINE"), + "VETERINARY MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", - "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") + "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); @@ -134,30 +134,30 @@ public void srscSearchTopTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"), + "HUMANITIES and RELIGION"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", - "Research Subject Categories::LAW/JURISPRUDENCE"), + "LAW/JURISPRUDENCE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", - "Research Subject Categories::SOCIAL SCIENCES"), + "SOCIAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", - "Research Subject Categories::MATHEMATICS"), + "MATHEMATICS"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", - "Research Subject Categories::NATURAL SCIENCES"), + "NATURAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", - "Research Subject Categories::TECHNOLOGY"), + "TECHNOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", - "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", - "Research Subject Categories::MEDICINE"), + "MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", - "Research Subject Categories::ODONTOLOGY"), + "ODONTOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", - "Research Subject Categories::PHARMACY"), + "PHARMACY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", - "Research Subject Categories::VETERINARY MEDICINE"), + "VETERINARY MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", - "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") + "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(12))); } @@ -170,14 +170,14 @@ public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", "Algebra, geometry and mathematical analysis", - "Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"), + "MATHEMATICS :: Algebra, geometry and mathematical analysis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", - "Research Subject Categories::MATHEMATICS::Applied mathematics"), + "MATHEMATICS :: Applied mathematics"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", - "Research Subject Categories::MATHEMATICS::Other mathematics") + "MATHEMATICS :: Other mathematics") ))) .andExpect(jsonPath("$._embedded.children[*].otherInformation.parent", - Matchers.everyItem(is("Research Subject Categories::MATHEMATICS")))) + Matchers.everyItem(is("MATHEMATICS")))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(3))); } @@ -191,15 +191,15 @@ public void srscSearchTopPaginationTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"), + "HUMANITIES and RELIGION"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE", - "Research Subject Categories::LAW/JURISPRUDENCE"), + "LAW/JURISPRUDENCE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES", - "Research Subject Categories::SOCIAL SCIENCES"), + "SOCIAL SCIENCES"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS", - "Research Subject Categories::MATHEMATICS"), + "MATHEMATICS"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES", - "Research Subject Categories::NATURAL SCIENCES") + "NATURAL SCIENCES") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -213,16 +213,16 @@ public void srscSearchTopPaginationTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY", - "Research Subject Categories::TECHNOLOGY"), + "TECHNOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17", "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING", - "Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), + "FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE", - "Research Subject Categories::MEDICINE"), + "MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY", - "Research Subject Categories::ODONTOLOGY"), + "ODONTOLOGY"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY", - "Research Subject Categories::PHARMACY") + "PHARMACY") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -236,9 +236,9 @@ public void srscSearchTopPaginationTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE", - "Research Subject Categories::VETERINARY MEDICINE"), + "VETERINARY MEDICINE"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS", - "Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS") + "INTERDISCIPLINARY RESEARCH AREAS") ))) .andExpect(jsonPath("$.page.totalElements", is(12))) .andExpect(jsonPath("$.page.totalPages", is(3))) @@ -271,9 +271,9 @@ public void srscSearchByParentFirstLevelPaginationTest() throws Exception { .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", "Algebra, geometry and mathematical analysis", - "Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"), + "MATHEMATICS :: Algebra, geometry and mathematical analysis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", - "Research Subject Categories::MATHEMATICS::Applied mathematics") + "MATHEMATICS :: Applied mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -286,7 +286,7 @@ public void srscSearchByParentFirstLevelPaginationTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.contains( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", - "Research Subject Categories::MATHEMATICS::Other mathematics") + "MATHEMATICS :: Other mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -300,13 +300,13 @@ public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exce .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140202", "Numerical analysis", - "Research Subject Categories::MATHEMATICS::Applied mathematics::Numerical analysis"), + "MATHEMATICS :: Applied mathematics :: Numerical analysis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140203", "Mathematical statistics", - "Research Subject Categories::MATHEMATICS::Applied mathematics::Mathematical statistics"), + "MATHEMATICS :: Applied mathematics :: Mathematical statistics"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140204", "Optimization, systems theory", - "Research Subject Categories::MATHEMATICS::Applied mathematics::Optimization, systems theory"), + "MATHEMATICS :: Applied mathematics :: Optimization, systems theory"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140205", "Theoretical computer science", - "Research Subject Categories::MATHEMATICS::Applied mathematics::Theoretical computer science") + "MATHEMATICS :: Applied mathematics :: Theoretical computer science") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); } @@ -341,7 +341,7 @@ public void findParentByChildTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$", is( VocabularyEntryDetailsMatcher.matchAuthorityEntry( - "srsc:SCB18", "MEDICINE","Research Subject Categories::MEDICINE") + "srsc:SCB18", "MEDICINE","MEDICINE") ))); } @@ -355,7 +355,7 @@ public void findParentByChildBadRequestTest() throws Exception { @Test public void findParentByChildUnauthorizedTest() throws Exception { getClient().perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) - .andExpect(status().isUnauthorized()); + .andExpect(status().isOk()); } @Test @@ -375,11 +375,11 @@ public void srscProjectionTest() throws Exception { .andExpect(jsonPath("$._embedded.parent", VocabularyEntryDetailsMatcher.matchAuthorityEntry( "srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"))) + "HUMANITIES and RELIGION"))) .andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children())) .andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent", Matchers.everyItem( - is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology")))); + is("HUMANITIES and RELIGION :: Religion/Theology")))); getClient(tokenAdmin).perform( get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "children")) @@ -387,7 +387,7 @@ public void srscProjectionTest() throws Exception { .andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children())) .andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent", Matchers.everyItem( - is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology")))); + is("HUMANITIES and RELIGION :: Religion/Theology")))); getClient(tokenAdmin).perform( get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "parent")) @@ -395,47 +395,47 @@ public void srscProjectionTest() throws Exception { .andExpect(jsonPath("$._embedded.parent", VocabularyEntryDetailsMatcher.matchAuthorityEntry( "srsc:SCB11", "HUMANITIES and RELIGION", - "Research Subject Categories::HUMANITIES and RELIGION"))); + "HUMANITIES and RELIGION"))); } private Matcher> matchAllSrscSC110Children() { return Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110102", "History of religion", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::History of religion"), + "HUMANITIES and RELIGION :: Religion/Theology :: History of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110103", "Church studies", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Church studies"), + "HUMANITIES and RELIGION :: Religion/Theology :: Church studies"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110104", "Missionary studies", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Missionary studies"), + "HUMANITIES and RELIGION :: Religion/Theology :: Missionary studies"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110105", "Systematic theology", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Systematic theology"), + "HUMANITIES and RELIGION :: Religion/Theology :: Systematic theology"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110106", "Islamology", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Islamology"), + "HUMANITIES and RELIGION :: Religion/Theology :: Islamology"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110107", "Faith and reason", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Faith and reason"), + "HUMANITIES and RELIGION :: Religion/Theology :: Faith and reason"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110108", "Sociology of religion", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Sociology of religion"), + "HUMANITIES and RELIGION :: Religion/Theology :: Sociology of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110109", "Psychology of religion", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Psychology of religion"), + "HUMANITIES and RELIGION :: Religion/Theology :: Psychology of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110110", "Philosophy of religion", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Philosophy of religion"), + "HUMANITIES and RELIGION :: Religion/Theology :: Philosophy of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110111", "New Testament exegesis", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::New Testament exegesis"), + "HUMANITIES and RELIGION :: Religion/Theology :: New Testament exegesis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110112", "Old Testament exegesis", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Old Testament exegesis"), + "HUMANITIES and RELIGION :: Religion/Theology :: Old Testament exegesis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110113", "Dogmatics with symbolics", - "Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Dogmatics with symbolics") + "HUMANITIES and RELIGION :: Religion/Theology :: Dogmatics with symbolics") ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 5ea8cdb2312c..a84e2242a3ea 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -179,9 +179,9 @@ public void correctSrscQueryTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder( VocabularyMatcher.matchVocabularyEntry("Research Subject Categories", - "Research Subject Categories", "vocabularyEntry"), + "", "vocabularyEntry"), VocabularyMatcher.matchVocabularyEntry("Family research", - "Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research", + "SOCIAL SCIENCES :: Social sciences :: Social work :: Family research", "vocabularyEntry")))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))) .andExpect(jsonPath("$.page.totalPages", Matchers.is(13))) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 02c618abf420..5c1fd9763cd9 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1386,7 +1386,7 @@ orcid.oauth.url = https://orcid.org/oauth/token plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ org.dspace.content.authority.DCInputAuthority, \ - org.dspace.content.authority.DSpaceControlledVocabulary + com.atmire.dspace.content.authority.WbDSpaceControlledVocabulary ## ## This sets the default lowest confidence level at which a metadata value is included From 188d02c82215f86e746309faac95c3dd2e5d02fd Mon Sep 17 00:00:00 2001 From: Samuel Date: Wed, 8 Dec 2021 10:09:41 +0100 Subject: [PATCH 068/394] taskid 85555 Custom DSpaceControlledVocabulary for WB syntax - permissions --- .../repository/VocabularyEntryDetailsRestRepository.java | 4 ++-- .../java/org/dspace/app/rest/VocabularyEntryDetailsIT.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java index 911a4ae49bdf..fdfa28dd05c6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryDetailsRestRepository.java @@ -60,7 +60,7 @@ public void afterPropertiesSet() throws Exception { VocabularyEntryDetailsRest.PLURAL_NAME + "-search"))); } - @PreAuthorize("permitAll()") + @PreAuthorize("hasAuthority('AUTHENTICATED')") @Override public Page findAll(Context context, Pageable pageable) { throw new RepositoryMethodNotImplementedException(ResourcePolicyRest.NAME, "findAll"); @@ -82,7 +82,7 @@ public VocabularyEntryDetailsRest findOne(Context context, String name) { } @SearchRestMethod(name = "top") - @PreAuthorize("hasAuthority('AUTHENTICATED')") + @PreAuthorize("permitAll()") public Page findAllTop(@Parameter(value = "vocabulary", required = true) String vocabularyId, Pageable pageable) { Context context = obtainContext(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index a580c28c92a6..fa277bf8a99e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -256,8 +256,8 @@ public void searchTopBadRequestTest() throws Exception { @Test public void searchTopUnauthorizedTest() throws Exception { getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") - .param("vocabulary", "srsc:SCB16")) - .andExpect(status().isUnauthorized()); + .param("vocabulary", "srsc")) + .andExpect(status().isOk()); } @Test @@ -331,7 +331,7 @@ public void srscSearchByParentWrongIdTest() throws Exception { public void srscSearchTopUnauthorizedTest() throws Exception { getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) - .andExpect(status().isUnauthorized()); + .andExpect(status().isOk()); } @Test From f065f12fa75f2392db49888f269a45daa872f2e5 Mon Sep 17 00:00:00 2001 From: Samuel Date: Wed, 8 Dec 2021 17:27:20 +0100 Subject: [PATCH 069/394] taskid 85816 Rename vocabularies to match the discovery config --- .../atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java index cd7baae91586..d22525223a64 100644 --- a/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java @@ -27,14 +27,14 @@ public void testCustomWbSyntax() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token) - .perform(get("/api/submission/vocabularyEntryDetails/topics:Information_Technology")) + .perform(get("/api/submission/vocabularyEntryDetails/topic:Information_Technology")) .andExpect(status().isOk()) .andExpect(jsonPath("$.value", is( "Information and Communication Technologies :: Information Technology" ))); getClient(token) - .perform(get("/api/submission/vocabularies/topics/entries") + .perform(get("/api/submission/vocabularies/topic/entries") .param("filter", "Information and Communication Technologies :: Information Technology") .param("exact", "true")) .andExpect(status().isOk()) @@ -43,7 +43,7 @@ public void testCustomWbSyntax() throws Exception { ))); getClient(token) - .perform(get("/api/submission/vocabularyEntryDetails/search/top?vocabulary=topics")) + .perform(get("/api/submission/vocabularyEntryDetails/search/top?vocabulary=topic")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", everyItem( hasJsonPath("$.value", not(startsWith("Topics:"))) From cfbd7785e5a4378d8db71ced59b76394a264723c Mon Sep 17 00:00:00 2001 From: Joost Date: Tue, 18 Jan 2022 09:46:35 +0100 Subject: [PATCH 070/394] [task 86387] made findone for vocabularies publicly available --- .../app/rest/repository/VocabularyRestRepository.java | 2 +- .../org/dspace/app/rest/VocabularyRestRepositoryIT.java | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java index dcdf71186bcb..fcc37d13160d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyRestRepository.java @@ -53,7 +53,7 @@ public class VocabularyRestRepository extends DSpaceRestRepository Date: Thu, 1 Dec 2022 15:36:16 +0100 Subject: [PATCH 071/394] 97049: Remove client structure --- .../WbDSpaceControlledVocabulary.java | 30 ------------------- .../authority/DSpaceControlledVocabulary.java | 6 +++- .../test/data/dspaceFolder/config/local.cfg | 2 +- dspace/config/dspace.cfg | 2 +- 4 files changed, 7 insertions(+), 33 deletions(-) delete mode 100644 dspace-api/src/main/java/com/atmire/dspace/content/authority/WbDSpaceControlledVocabulary.java diff --git a/dspace-api/src/main/java/com/atmire/dspace/content/authority/WbDSpaceControlledVocabulary.java b/dspace-api/src/main/java/com/atmire/dspace/content/authority/WbDSpaceControlledVocabulary.java deleted file mode 100644 index ed05325ace20..000000000000 --- a/dspace-api/src/main/java/com/atmire/dspace/content/authority/WbDSpaceControlledVocabulary.java +++ /dev/null @@ -1,30 +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 com.atmire.dspace.content.authority; - -import org.dspace.content.authority.DSpaceControlledVocabulary; -import org.w3c.dom.Node; - -public class WbDSpaceControlledVocabulary extends DSpaceControlledVocabulary { - - @Override - protected void init() { - super.init(); - hierarchyDelimiter = " " + hierarchyDelimiter + " "; - } - - protected String buildString(Node node) { - if (node.getNodeType() == Node.DOCUMENT_NODE || ( - node.getParentNode() != null && - node.getParentNode().getNodeType() == Node.DOCUMENT_NODE)) { - return (""); - } else { - return super.buildString(node); - } - } -} diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index dfaf4a107fde..d5e350faf0ec 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -132,11 +132,15 @@ protected void init() { String filename = vocabulariesPath + vocabularyName + ".xml"; log.info("Loading " + filename); vocabulary = new InputSource(filename); + + hierarchyDelimiter = " " + hierarchyDelimiter + " "; } } protected String buildString(Node node) { - if (node.getNodeType() == Node.DOCUMENT_NODE) { + if (node.getNodeType() == Node.DOCUMENT_NODE || ( + node.getParentNode() != null && + node.getParentNode().getNodeType() == Node.DOCUMENT_NODE)) { return (""); } else { String parentValue = buildString(node.getParentNode()); diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 74e3e14d6a8b..3c19a68e9fd1 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -97,7 +97,7 @@ event.dispatcher.exclude-discovery.consumers = versioning, eperson # (This overrides default, commented out settings in dspace.cfg) plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ org.dspace.content.authority.DCInputAuthority, \ - com.atmire.dspace.content.authority.WbDSpaceControlledVocabulary + org.dspace.content.authority.DSpaceControlledVocabulary # Configure some more Plugins for PluginTest class # NOTE: Plugins are just *interfaces*. So, here we are defining some plugins diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 5c1fd9763cd9..02c618abf420 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1386,7 +1386,7 @@ orcid.oauth.url = https://orcid.org/oauth/token plugin.selfnamed.org.dspace.content.authority.ChoiceAuthority = \ org.dspace.content.authority.DCInputAuthority, \ - com.atmire.dspace.content.authority.WbDSpaceControlledVocabulary + org.dspace.content.authority.DSpaceControlledVocabulary ## ## This sets the default lowest confidence level at which a metadata value is included From 6621e57ebd4b1a94170a3510718098cf29c39dbb Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Thu, 1 Dec 2022 16:37:03 +0100 Subject: [PATCH 072/394] 97183: Added SolrServiceIndexItemEditorsPlugin Analogous to SolrServiceIndexCollectionSubmittersPlugin, but for Items instead of Collections, and for WRITE rights instead of ADD rights. Also refactored SolrServiceIndexCollectionSubmittersPlugin to share code with the new plugin. --- .../org/dspace/discovery/IndexingUtils.java | 146 ++++++++++++++++++ ...erviceIndexCollectionSubmittersPlugin.java | 42 ++--- .../SolrServiceIndexItemEditorsPlugin.java | 63 ++++++++ .../AuthorizationFeatureService.java | 6 +- .../impl/AuthorizationFeatureServiceImpl.java | 3 +- .../AuthorizationRestRepository.java | 11 +- dspace/solr/search/conf/schema.xml | 3 + 7 files changed, 237 insertions(+), 37 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java create mode 100644 dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java new file mode 100644 index 000000000000..35835ec402c3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java @@ -0,0 +1,146 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.discovery; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; + +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.core.Constants; +import org.dspace.core.Context; + +/** + * Util methods used by indexing. + * + * @author Koen Pauwels (koen.pauwels at atmire dot com) + */ +public abstract class IndexingUtils { + /** + * Retrieve all ancestor communities of a given community, with the first one being the given community and the + * last one being the root. + * + * TODO: can be done in a single SQL query with recursive common table expressions + * TODO: should probably be moved to CommunityService + * + * @param context DSpace context object + * @param community Community for which we search the ancestors + * @return A stream of ancestor communities. + * @throws SQLException if database error + */ + static Stream getAncestorCommunities(Context context, Community community) throws SQLException { + ArrayList communities = new ArrayList<>(); + while (community != null) { + communities.add(community); + community = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(community) + .getParentObject(context, community); + } + return communities.stream(); + } + + /** + * Retrieve the ids of all groups that have ADMIN rights to the given community, either directly + * (through direct resource policy) or indirectly (through a policy on an ancestor community). + * + * @param context DSpace context object + * @param community Community for which we search the admin group IDs + * @return A stream of admin group IDs + * @throws SQLException if database error + */ + static Stream findTransitiveAdminGroupIds(Context context, Community community) throws SQLException { + return getAncestorCommunities(context, community) + .map(parent -> parent.getAdministrators().getID()); + } + + /** + * Retrieve the ids of all groups that have ADMIN rights to the given collection, either directly + * (through direct resource policy) or indirectly (through a policy on its community, or one of + * its ancestor communities). + * + * @param context DSpace context object + * @param collection Collection for which we search the admin group IDs + * @return A stream of admin group IDs + * @throws SQLException if database error + */ + static Stream findTransitiveAdminGroupIds(Context context, Collection collection) throws SQLException { + UUID directAdminGroupId = collection.getAdministrators().getID(); + List> subResults = Arrays.asList(Stream.of(directAdminGroupId)); + for (Community community : collection.getCommunities()) { + subResults.add(findTransitiveAdminGroupIds(context, community)); + } + return sequence(subResults); + } + + /** + * Retrieve the ids of all groups that have ADMIN rights on the given item, either directly + * (through direct resource policy) or indirectly (through a policy on the owning collection, or on + * the owning collection's community, or on any of that community's ancestor communities). + * + * @param authService + * @param context + * @param item + * @return + * @throws SQLException + * + * @param authService The authentication service + * @param context DSpace context object + * @param item Item for which we search the admin group IDs + * @return A stream of admin group IDs + * @throws SQLException if database error + */ + static Stream findTransitiveAdminGroupIds(AuthorizeService authService, Context context, Item item) + throws SQLException { + Stream directAdminGroupIds = authService.getPoliciesActionFilter(context, item, Constants.ADMIN) + .stream() + .filter(policy -> policy.getGroup() != null) + .map(policy -> policy.getGroup().getID()); + List> subResults = Arrays.asList(directAdminGroupIds); + for (Collection coll : item.getCollections()) { + subResults.add(findTransitiveAdminGroupIds(context, coll)); + } + return sequence(subResults); + } + + /** + * Retrieve group and eperson IDs for all groups and eperson who have _any_ of the given authorizations + * on the given DSpaceObject. The resulting IDs are prefixed with "e" in the case of an eperson ID, and "g" in the + * case of a group ID. + * + * @param authService The authentication service + * @param context DSpace context object + * @param obj DSpaceObject for which we search the admin group IDs + * @return A stream of admin group IDs as Strings, prefixed with either "e" or "g", depending on whether it is a + * group or eperson ID. + * @throws SQLException if database error + */ + static Stream findDirectAuthorizedGroupsAndEPersonsPrefixedIds(AuthorizeService authService, + Context context, DSpaceObject obj, int[] authorizations) throws SQLException { + + ArrayList> subResults = new ArrayList<>(); + for (int auth : authorizations) { + Stream subResult = authService.getPoliciesActionFilter(context, obj, auth).stream() + .map(policy -> policy.getGroup() == null ? "e" + policy.getEPerson().getID() + : "g" + policy.getGroup().getID()); + subResults.add(subResult); + // TODO: context.uncacheEntitiy(policy); + } + return sequence(subResults); + } + + private static Stream sequence(List> subResults) { + return subResults.stream().flatMap(x -> x); + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java index 00b70f93d50e..5b97c1edfe03 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java @@ -8,15 +8,11 @@ package org.dspace.discovery; import java.sql.SQLException; -import java.util.List; import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrInputDocument; -import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogHelper; @@ -42,30 +38,18 @@ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo Collection col = ((IndexableCollection) idxObj).getIndexedObject(); if (col != null) { try { - String fieldValue = null; - Community parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(col) - .getParentObject(context, col); - while (parent != null) { - if (parent.getAdministrators() != null) { - fieldValue = "g" + parent.getAdministrators().getID(); - document.addField("submit", fieldValue); - } - parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(parent) - .getParentObject(context, parent); - } - List policies = authorizeService.getPoliciesActionFilter(context,col,Constants.ADD); - policies.addAll(authorizeService.getPoliciesActionFilter(context, col, Constants.ADMIN)); - - for (ResourcePolicy resourcePolicy : policies) { - if (resourcePolicy.getGroup() != null) { - fieldValue = "g" + resourcePolicy.getGroup().getID(); - } else { - fieldValue = "e" + resourcePolicy.getEPerson().getID(); - - } - document.addField("submit", fieldValue); - context.uncacheEntity(resourcePolicy); - } + // Index groups with ADMIN rights on the Collection, on + // Communities containing those Collections, and recursively on any Community containing such a + // Community. + // TODO: Strictly speaking we should also check for epersons who received admin rights directly, + // without being part of the admin group. Finding them may be a lot slower though. + IndexingUtils.findTransitiveAdminGroupIds(context, col) + .forEach(unprefixedId -> document.addField("submit", "g" + unprefixedId)); + + // Index groups and epersons with ADD rights on the Collection. + IndexingUtils.findDirectAuthorizedGroupsAndEPersonsPrefixedIds( + authorizeService, context, col, new int[] {Constants.ADD} + ).forEach(prefixedId -> document.addField("submit", prefixedId)); } catch (SQLException e) { log.error(LogHelper.getHeader(context, "Error while indexing resource policies", "Collection: (id " + col.getID() + " type " + col.getName() + ")" )); @@ -74,4 +58,4 @@ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo } } -} \ No newline at end of file +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java new file mode 100644 index 000000000000..d4b253851939 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java @@ -0,0 +1,63 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.discovery; + +import static org.dspace.discovery.IndexingUtils.findDirectAuthorizedGroupsAndEPersonsPrefixedIds; +import static org.dspace.discovery.IndexingUtils.findTransitiveAdminGroupIds; + +import java.sql.SQLException; + +import org.apache.logging.log4j.Logger; +import org.apache.solr.common.SolrInputDocument; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.core.LogHelper; +import org.dspace.discovery.indexobject.IndexableItem; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Indexes policies that yield write access to items. + * + * @author Koen Pauwels at atmire.com + */ +public class SolrServiceIndexItemEditorsPlugin implements SolrServiceIndexPlugin { + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(SolrServiceIndexCollectionSubmittersPlugin.class); + + @Autowired(required = true) + protected AuthorizeService authorizeService; + + @Override + public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) { + if (idxObj instanceof IndexableItem) { + Item item = ((IndexableItem) idxObj).getIndexedObject(); + if (item != null) { + try { + // Index groups with ADMIN rights on the Item, on Collections containing the Item, on + // Communities containing those Collections, and recursively on any Community containing ssuch a + // Community. + // TODO: Strictly speaking we should also check for epersons who received admin rights directly, + // without being part of the admin group. Finding them may be a lot slower though. + findTransitiveAdminGroupIds(authorizeService, context, item) + .forEach(unprefixedId -> document.addField("edit", "g" + unprefixedId)); + + // Index groups and epersons with WRITE rights on the Item. + findDirectAuthorizedGroupsAndEPersonsPrefixedIds( + authorizeService, context, item, new int[] {Constants.WRITE} + ).forEach(prefixedId -> document.addField("edit", prefixedId)); + + } catch (SQLException e) { + log.error(LogHelper.getHeader(context, "Error while indexing resource policies", + "Item: (id " + item.getID() + " name " + item.getName() + ")" )); + } + } + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeatureService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeatureService.java index 2b04bb983c29..941c614fcb37 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeatureService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/AuthorizationFeatureService.java @@ -13,6 +13,7 @@ import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.SiteRest; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; /** * This service provides access to the Authorization Features and check if the feature is allowed or not in a specific @@ -34,7 +35,8 @@ public interface AuthorizationFeatureService { * feature pass the {@link SiteRest} object * @return true if the user associated with the context has access to the feature */ - boolean isAuthorized(Context context, AuthorizationFeature feature, BaseObjectRest object) throws SQLException; + boolean isAuthorized(Context context, AuthorizationFeature feature, BaseObjectRest object) + throws SQLException, SearchServiceException; /** * Get all the authorization features defined in the system @@ -60,4 +62,4 @@ public interface AuthorizationFeatureService { * @return */ List findByResourceType(String categoryDotModel); -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/AuthorizationFeatureServiceImpl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/AuthorizationFeatureServiceImpl.java index 01385d443565..766204364c73 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/AuthorizationFeatureServiceImpl.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/AuthorizationFeatureServiceImpl.java @@ -18,6 +18,7 @@ import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.utils.Utils; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -38,7 +39,7 @@ public class AuthorizationFeatureServiceImpl implements AuthorizationFeatureServ @Override public boolean isAuthorized(Context context, AuthorizationFeature feature, BaseObjectRest object) - throws SQLException { + throws SQLException, SearchServiceException { if (object == null) { // the authorization interface require that the object is not null return false; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java index 97e6866073f6..137952866959 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java @@ -31,6 +31,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.slf4j.Logger; @@ -124,7 +125,7 @@ public AuthorizationRest findOne(Context context, String id) { // restore the real current user context.restoreContextUser(); } - } catch (SQLException e) { + } catch (SQLException | SearchServiceException e) { throw new RuntimeException(e.getMessage(), e); } @@ -151,7 +152,7 @@ public AuthorizationRest findOne(Context context, String id) { @SearchRestMethod(name = "object") public Page findByObject(@Parameter(value = "uri", required = true) String uri, @Parameter(value = "eperson") UUID epersonUuid, @Parameter(value = "feature") String featureName, - Pageable pageable) throws AuthorizeException, SQLException { + Pageable pageable) throws AuthorizeException, SQLException, SearchServiceException { Context context = obtainContext(); @@ -234,7 +235,7 @@ private List findAuthorizationsForUri( Context context, EPerson user, String uri, - String featureName) throws SQLException { + String featureName) throws SQLException, SearchServiceException { BaseObjectRest restObject = utils.getBaseObjectRestFromUri(context, uri); return authorizationsForObject(context, user, featureName, restObject); @@ -244,7 +245,7 @@ private List authorizationsForObject( Context context, EPerson user, String featureName, BaseObjectRest obj) - throws SQLException { + throws SQLException, SearchServiceException { if (obj == null) { return new ArrayList<>(); @@ -269,7 +270,7 @@ private List authorizationsForObject( private List findByObjectAndFeature( Context context, EPerson user, BaseObjectRest obj, String featureName - ) throws SQLException { + ) throws SQLException, SearchServiceException { AuthorizationFeature feature = authorizationFeatureService.find(featureName); diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index f9c8eb97601b..f8878b37b2be 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -264,6 +264,9 @@ + + + From f7e7f69f2d71523d4a760fad2e51d96888dfecc1 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Thu, 1 Dec 2022 17:49:15 +0100 Subject: [PATCH 073/394] 97049: Remove client specific test --- .../app/rest/WbVocabularyEntryDetailsIT.java | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java diff --git a/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java deleted file mode 100644 index d22525223a64..000000000000 --- a/dspace-server-webapp/src/test/java/com/atmire/dspace/app/rest/WbVocabularyEntryDetailsIT.java +++ /dev/null @@ -1,52 +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 com.atmire.dspace.app.rest; - -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.hamcrest.Matchers.everyItem; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.junit.Test; - -public class WbVocabularyEntryDetailsIT extends AbstractControllerIntegrationTest { - - @Test - public void testCustomWbSyntax() throws Exception { - - String token = getAuthToken(eperson.getEmail(), password); - - getClient(token) - .perform(get("/api/submission/vocabularyEntryDetails/topic:Information_Technology")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.value", is( - "Information and Communication Technologies :: Information Technology" - ))); - - getClient(token) - .perform(get("/api/submission/vocabularies/topic/entries") - .param("filter", "Information and Communication Technologies :: Information Technology") - .param("exact", "true")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.entries[0].value", is( - "Information and Communication Technologies :: Information Technology" - ))); - - getClient(token) - .perform(get("/api/submission/vocabularyEntryDetails/search/top?vocabulary=topic")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.vocabularyEntryDetails", everyItem( - hasJsonPath("$.value", not(startsWith("Topics:"))) - ))); - } -} From c535de90f189bf4fff88e5712f296da55e77d45a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 1 Dec 2022 18:25:00 +0100 Subject: [PATCH 074/394] [DSC-183] Created Subscription_Parameter, Changed Subscription and created entry points components for Subscription --- .../FrequencySubscriptionParameter.java | 12 ++ .../org/dspace/eperson/SubscribeCLITool.java | 2 +- .../dspace/eperson/SubscribeServiceImpl.java | 87 +++++++-- .../java/org/dspace/eperson/Subscription.java | 48 ++++- .../dspace/eperson/SubscriptionParameter.java | 70 +++++++ .../dspace/eperson/dao/SubscriptionDAO.java | 7 +- .../eperson/dao/SubscriptionParameterDAO.java | 22 +++ .../eperson/dao/impl/SubscriptionDAOImpl.java | 22 +-- .../impl/SubscriptionParameterDAOImpl.java | 25 +++ .../eperson/service/SubscribeService.java | 48 ++++- ...mter_change_columns_subscription_table.sql | 35 ++++ ...mter_change_columns_subscription_table.sql | 37 ++++ ...mter_change_columns_subscription_table.sql | 35 ++++ .../rest/converter/SubscriptionConverter.java | 50 +++++ .../app/rest/model/SubscriptionRest.java | 78 ++++++++ .../model/hateoas/SubscriptionResource.java | 22 +++ ...ubscriptionDSpaceObjectLinkRepository.java | 63 +++++++ .../SubscriptionEPersonLinkRepository.java | 51 ++++++ .../SubscriptionRestRepository.java | 171 ++++++++++++++++++ dspace/config/hibernate.cfg.xml | 2 +- 20 files changed, 833 insertions(+), 54 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/eperson/FrequencySubscriptionParameter.java create mode 100644 dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java create mode 100644 dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java create mode 100644 dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java diff --git a/dspace-api/src/main/java/org/dspace/eperson/FrequencySubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/FrequencySubscriptionParameter.java new file mode 100644 index 000000000000..e735c969482f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/FrequencySubscriptionParameter.java @@ -0,0 +1,12 @@ +package org.dspace.eperson; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +/** + * @author Alba Aliu (alba.aliu at alba.aliu@atis.al) + * + */ +@Entity +@DiscriminatorValue("FREQUENCY") +public class FrequencySubscriptionParameter extends SubscriptionParameter { +} diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java index 9e5ecaa4fb0a..b8f5d8be346f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java @@ -120,7 +120,7 @@ public static void processDaily(Context context, boolean test) throws SQLExcepti collections = new ArrayList<>(); } - collections.add(subscription.getCollection()); + collections.add((Collection) subscription.getdSpaceObject()); } // Process the last person diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 81c367f0eac2..21890baf9e17 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -2,7 +2,7 @@ * 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.eperson; @@ -14,6 +14,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -36,7 +37,6 @@ public class SubscribeServiceImpl implements SubscribeService { @Autowired(required = true) protected SubscriptionDAO subscriptionDAO; - @Autowired(required = true) protected AuthorizeService authorizeService; @Autowired(required = true) @@ -52,20 +52,27 @@ public List findAll(Context context) throws SQLException { } @Override - public void subscribe(Context context, EPerson eperson, - Collection collection) throws SQLException, AuthorizeException { + public Subscription subscribe(Context context, EPerson eperson, + DSpaceObject dSpaceObject, + List subscriptionParameterList, + String type) throws SQLException, AuthorizeException { // Check authorisation. Must be administrator, or the eperson. if (authorizeService.isAdmin(context) - || ((context.getCurrentUser() != null) && (context - .getCurrentUser().getID().equals(eperson.getID())))) { - if (!isSubscribed(context, eperson, collection)) { - Subscription subscription = subscriptionDAO.create(context, new Subscription()); - subscription.setCollection(collection); - subscription.setePerson(eperson); + || ((context.getCurrentUser() != null) && (context + .getCurrentUser().getID().equals(eperson.getID())))) { + if (!isSubscribed(context, eperson, dSpaceObject)) { + Subscription new_subscription = subscriptionDAO.create(context, new Subscription()); + new_subscription.setSubscriptionParameterList(subscriptionParameterList); + new_subscription.setePerson(eperson); + new_subscription.setdSpaceObject(dSpaceObject); + new_subscription.setType(type); + return new_subscription; + } else { + throw new IllegalArgumentException("Subscription already exists"); } } else { throw new AuthorizeException( - "Only admin or e-person themselves can subscribe"); + "Only admin or e-person themselves can subscribe"); } } @@ -74,8 +81,8 @@ public void unsubscribe(Context context, EPerson eperson, Collection collection) throws SQLException, AuthorizeException { // Check authorisation. Must be administrator, or the eperson. if (authorizeService.isAdmin(context) - || ((context.getCurrentUser() != null) && (context - .getCurrentUser().getID().equals(eperson.getID())))) { + || ((context.getCurrentUser() != null) && (context + .getCurrentUser().getID().equals(eperson.getID())))) { if (collection == null) { // Unsubscribe from all subscriptionDAO.deleteByEPerson(context, eperson); @@ -88,25 +95,25 @@ public void unsubscribe(Context context, EPerson eperson, } } else { throw new AuthorizeException( - "Only admin or e-person themselves can unsubscribe"); + "Only admin or e-person themselves can unsubscribe"); } } @Override public List getSubscriptions(Context context, EPerson eperson) - throws SQLException { + throws SQLException { return subscriptionDAO.findByEPerson(context, eperson); } @Override public List getAvailableSubscriptions(Context context) - throws SQLException { + throws SQLException { return getAvailableSubscriptions(context, null); } @Override public List getAvailableSubscriptions(Context context, EPerson eperson) - throws SQLException { + throws SQLException { List collections; if (eperson != null) { context.setCurrentUser(eperson); @@ -118,8 +125,8 @@ public List getAvailableSubscriptions(Context context, EPerson epers @Override public boolean isSubscribed(Context context, EPerson eperson, - Collection collection) throws SQLException { - return subscriptionDAO.findByCollectionAndEPerson(context, eperson, collection) != null; + DSpaceObject dSpaceObject) throws SQLException { + return subscriptionDAO.findByCollectionAndEPerson(context, eperson, dSpaceObject) != null; } @Override @@ -131,4 +138,46 @@ public void deleteByCollection(Context context, Collection collection) throws SQ public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException { subscriptionDAO.deleteByEPerson(context, ePerson); } + + @Override + public Subscription findById(Context context, int id) throws SQLException { + return subscriptionDAO.findByID(context, Subscription.class, id); + } + + @Override + public Subscription updateSubscription(Context context, Integer id, + EPerson eperson, + DSpaceObject dSpaceObject, + List subscriptionParameterList, + String type) throws SQLException, AuthorizeException { + // must be admin or the subscriber of the subscription + if (authorizeService.isAdmin(context, context.getCurrentUser()) || eperson.equals(context.getCurrentUser())) { + Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); + subscription.setType(type); + subscription.setdSpaceObject(dSpaceObject); + subscription.setSubscriptionParameterList(subscriptionParameterList); + subscription.setePerson(eperson); + subscriptionDAO.save(context, subscription); + return subscription; + } else { + throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); + } + } + + @Override + public void deleteSubscription(Context context, Integer id) throws SQLException, AuthorizeException { + // initially find the eperson associated with the subscription + Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); + if (subscription != null) { + // must be admin or the subscriber of the subscription + if (authorizeService.isAdmin(context, context.getCurrentUser()) || subscription.getePerson().equals(context.getCurrentUser())) { + subscriptionDAO.delete(context, subscription); + } else { + throw new AuthorizeException("Only admin or e-person themselves can delete the subscription"); + } + } else { + throw new IllegalArgumentException("Subscription with id" + id + "is not found"); + } + + } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index 1719888ca8f4..ed1f50dd94a1 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -7,6 +7,7 @@ */ package org.dspace.eperson; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -15,13 +16,19 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; import javax.persistence.SequenceGenerator; import javax.persistence.Table; import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; +import java.util.ArrayList; +import java.util.List; + /** * Database entity representation of the subscription table * @@ -38,16 +45,21 @@ public class Subscription implements ReloadableEntity { private Integer id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "collection_id") - private Collection collection; + @JoinColumn(name = "dspace_object_id") + private DSpaceObject dSpaceObject; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "eperson_id") private EPerson ePerson; + @Column(name = "type") + private String type; + + @OneToMany(fetch = FetchType.EAGER, mappedBy = "subscription", cascade = CascadeType.ALL) + private List subscriptionParameterList = new ArrayList<>(); /** * Protected constructor, create object using: - * {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, Collection)} + * {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, DSpaceObject, List, String)} */ protected Subscription() { @@ -58,19 +70,39 @@ public Integer getID() { return id; } - public Collection getCollection() { - return collection; + public DSpaceObject getdSpaceObject() { + return this.dSpaceObject; } - void setCollection(Collection collection) { - this.collection = collection; + void setDSpaceObject(DSpaceObject dSpaceObject) { + this.dSpaceObject = dSpaceObject; } public EPerson getePerson() { return ePerson; } - void setePerson(EPerson ePerson) { + public void setePerson(EPerson ePerson) { this.ePerson = ePerson; } + + public void setdSpaceObject(DSpaceObject dSpaceObject) { + this.dSpaceObject = dSpaceObject; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public List getSubscriptionParameterList() { + return subscriptionParameterList; + } + + public void setSubscriptionParameterList(List subscriptionList) { + this.subscriptionParameterList = subscriptionList; + } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java new file mode 100644 index 000000000000..c34dcb9c5dfb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.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.eperson; + + +import javax.persistence.Column; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +/** + * Database entity representation of the subscription_parameter table + * + * @author Alba Aliu at atis.al + */ + +@Entity +@Table(name = "subscription_parameter") +@DiscriminatorColumn(name = "name") +public class SubscriptionParameter { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "subscription_parameter_seq") + @SequenceGenerator(name = "subscription_parameter_seq", sequenceName = "subscription_parameter_seq", allocationSize = 1) + @Column(name = "subscription_parameter_id", unique = true, nullable = false, insertable = true) + private Integer id; + @ManyToOne + @JoinColumn(name = "subscription_id") + private Subscription subscription; + @Column + private String name; + @Column + private String value; + + + public Subscription getSubscription() { + return subscription; + } + + public void setSubscription(Subscription subscription) { + this.subscription = subscription; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index e9f2d5705900..2547657ef6cd 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -11,6 +11,7 @@ import java.util.List; import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.GenericDAO; import org.dspace.eperson.EPerson; @@ -30,13 +31,13 @@ public interface SubscriptionDAO extends GenericDAO { public List findByEPerson(Context context, EPerson eperson) throws SQLException; - public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, Collection collection) - throws SQLException; + public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, DSpaceObject dSpaceObject) + throws SQLException; public void deleteByEPerson(Context context, EPerson eperson) throws SQLException; public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson) - throws SQLException; + throws SQLException; public List findAllOrderedByEPerson(Context context) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java new file mode 100644 index 000000000000..3a8556472d45 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java @@ -0,0 +1,22 @@ +/** + * 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.eperson.dao; + +import org.dspace.core.GenericDAO; +import org.dspace.eperson.SubscriptionParameter; + +/** + * Database Access Object interface class for the SubscriptionParamter object. + * The implementation of this class is responsible for all database calls for the SubscriptionParameter object and is + * autowired by spring + * This class should only be accessed from a single service and should never be exposed outside of the API + * + * @author Alba Aliu at atis.al + */ +public interface SubscriptionParameterDAO extends GenericDAO { +} diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index 6f2cb4b4fb7b..81428f16b52b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -16,6 +16,7 @@ import javax.persistence.criteria.Root; import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -47,17 +48,16 @@ public List findByEPerson(Context context, EPerson eperson) throws } @Override - public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, Collection collection) - throws SQLException { + public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, DSpaceObject dSpaceObject) + throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); - criteriaQuery - .where(criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson), - criteriaBuilder.equal(subscriptionRoot.get(Subscription_.collection), collection) - ) - ); + criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson), + criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) + ) + ); return singleResult(context, criteriaQuery); } @@ -80,7 +80,7 @@ public void deleteByEPerson(Context context, EPerson eperson) throws SQLExceptio @Override public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson) - throws SQLException { + throws SQLException { String hqlQuery = "delete from Subscription where collection=:collection AND ePerson=:ePerson"; Query query = createQuery(context, hqlQuery); query.setParameter("collection", collection); @@ -90,17 +90,13 @@ public void deleteByCollectionAndEPerson(Context context, Collection collection, @Override public List findAllOrderedByEPerson(Context context) throws SQLException { - - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); - - List orderList = new ArrayList<>(1); + List orderList = new LinkedList<>(); orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.ePerson))); criteriaQuery.orderBy(orderList); - return list(context, criteriaQuery, false, Subscription.class, -1, -1); } } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java new file mode 100644 index 000000000000..a13696cc5aa6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.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.eperson.dao.impl; + +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.dao.SubscriptionParameterDAO; + +/** + * Hibernate implementation of the Database Access Object interface class for the SubscriptionParameter object. + * This class is responsible for all database calls for the SubscriptionParameter object and is autowired by spring + * This class should never be accessed directly. + * + * @author Alba Aliu at atis.al + */ +public class SubscriptionParameterDAOImpl extends AbstractHibernateDAO implements SubscriptionParameterDAO { + protected SubscriptionParameterDAOImpl() { + super(); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 347c69bf5b0e..5ff92eab5bbf 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -2,7 +2,7 @@ * 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.eperson.service; @@ -12,9 +12,11 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; /** * Service interface class for the Subscription object. @@ -41,14 +43,14 @@ public interface SubscribeService { * new item appears in the collection. * * @param context DSpace context - * @param eperson EPerson to subscribe - * @param collection Collection to subscribe to * @throws SQLException An exception that provides information on a database access error or other errors. * @throws AuthorizeException Exception indicating the current user of the context does not have permission * to perform a particular action. */ - public void subscribe(Context context, EPerson eperson, - Collection collection) throws SQLException, AuthorizeException; + public Subscription subscribe(Context context, EPerson eperson, + DSpaceObject dSpaceObject, + List subscriptionParameterList, + String type ) throws SQLException, AuthorizeException; /** * Unsubscribe an e-person to a collection. Passing in null @@ -83,7 +85,7 @@ public void unsubscribe(Context context, EPerson eperson, * @throws SQLException An exception that provides information on a database access error or other errors. */ public List getAvailableSubscriptions(Context context) - throws SQLException; + throws SQLException; /** * Find out which collections an e-person can subscribe to @@ -94,19 +96,19 @@ public List getAvailableSubscriptions(Context context) * @throws SQLException An exception that provides information on a database access error or other errors. */ public List getAvailableSubscriptions(Context context, EPerson eperson) - throws SQLException; + throws SQLException; /** * Is that e-person subscribed to that collection? * * @param context DSpace context * @param eperson find out if this e-person is subscribed - * @param collection find out if subscribed to this collection + * @param dSpaceObject find out if subscribed to this dSpaceObject * @return true if they are subscribed * @throws SQLException An exception that provides information on a database access error or other errors. */ public boolean isSubscribed(Context context, EPerson eperson, - Collection collection) throws SQLException; + DSpaceObject dSpaceObject) throws SQLException; /** * Delete subscription by collection. @@ -125,4 +127,32 @@ public boolean isSubscribed(Context context, EPerson eperson, * @throws SQLException An exception that provides information on a database access error or other errors. */ public void deleteByEPerson(Context context, EPerson ePerson) throws SQLException; + + /** + * Finds a subscription by id + * + * @param context DSpace context + * @param id the id of subscription to be searched + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public Subscription findById(Context context, int id) throws SQLException; + + /** + * Updates a subscription by id + * + * @param context DSpace context + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public Subscription updateSubscription(Context context, Integer id, EPerson eperson, + DSpaceObject dSpaceObject, + List subscriptionParameterList, + String type) throws SQLException, AuthorizeException; + + /** + * Deletes a subscription + * + * @param context DSpace context + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public void deleteSubscription(Context context, Integer id) throws SQLException, AuthorizeException; } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql new file mode 100644 index 000000000000..69413b880796 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -0,0 +1,35 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +----------------------------------------------------------------------------------- +-- ADD table subscription_parameter +----------------------------------------------------------------------------------- + + +CREATE SEQUENCE subscription_parameter_seq; +CREATE TABLE subscription_parameter +( + subscription_parameter_id INTEGER NOT NULL, + parameter_name CHARACTER VARYING(255), + parameter_value CHARACTER VARYING(255), + subscription_id INTEGER NOT NULL, + CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), + CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) +); +-- -- +--ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; +---- -- +--ALTER TABLE subscription ALTER COLUMN collection_id RENAME TO dspace_object_id; +-- -- +ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); +---- -- +--ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject(uuid); + + + + diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql new file mode 100644 index 000000000000..1fc1935eab0f --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -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/ +-- + +----------------------------------------------------------------------------------- +-- ADD table subscription_parameter +----------------------------------------------------------------------------------- + + +CREATE SEQUENCE subscription_parameter_seq; +CREATE TABLE subscription_parameter +( + subscription_parameter_id INTEGER NOT NULL, + name VARCHAR(255), + value VARCHAR(255), + subscription_id INTEGER NOT NULL, + CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), + CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) + REFERENCES subscription (subscription_id) +); +-- -- + +--ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey +------ -- +--ALTER TABLE subscription ALTER COLUMN collection_id RENAME TO dspace_object_id; +---- -- +ALTER TABLE subscription ADD COL +---- -- +--ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); +-- + + + diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql new file mode 100644 index 000000000000..bc382613ab54 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -0,0 +1,35 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +----------------------------------------------------------------------------------- +-- ADD table subscription_parameter +----------------------------------------------------------------------------------- + + +CREATE SEQUENCE subscription_parameter_seq; +CREATE TABLE subscription_parameter +( + subscription_parameter_id INTEGER NOT NULL, + name CHARACTER VARYING(255), + value CHARACTER VARYING(255), + subscription_id INTEGER NOT NULL, + CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), + CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) +); +-- -- +--ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; +---- -- +--ALTER TABLE subscription RENAME COLUMN collection_id TO dspace_object_id; +---- -- +ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); +---- -- +--ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); +-- +-- +-- + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java new file mode 100644 index 000000000000..6673f00cb129 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java @@ -0,0 +1,50 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.Utils; +import org.dspace.eperson.Subscription; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This is the converter from Entity CrisLayoutTab to the REST data model + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * + */ +@Component +public class SubscriptionConverter implements DSpaceConverter { + @Autowired + protected Utils utils; + /* (non-Javadoc) + * @see org.dspace.app.rest.converter.DSpaceConverter#convert + * (java.lang.Object, org.dspace.app.rest.projection.Projection) + */ + @Autowired + private ConverterService converter; + @Override + public SubscriptionRest convert(Subscription subscription, Projection projection) { + SubscriptionRest rest = new SubscriptionRest(); + rest.setId(subscription.getID()); + rest.setSubscriptionParameterList(subscription.getSubscriptionParameterList()); + rest.setType(subscription.getType()); + return rest; + } + + /* (non-Javadoc) + * @see org.dspace.app.rest.converter.DSpaceConverter#getModelClass() + */ + @Override + public Class getModelClass() { + return Subscription.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java new file mode 100644 index 000000000000..5bd9f074b475 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -0,0 +1,78 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ + +package org.dspace.app.rest.model; + +import org.dspace.app.rest.RestResourceController; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; + +import java.util.ArrayList; +import java.util.List; + +@LinksRest(links = { + @LinkRest( + name = SubscriptionRest.DSPACE_OBJECT, + method = "getDSpaceObject" + ), + @LinkRest( + name = SubscriptionRest.EPERSON, + method = "getEPerson" + ) +}) +public class SubscriptionRest extends BaseObjectRest{ + public static final String NAME = "subscription"; + public static final String NAME_PLURAL = "subscriptions"; + public static final String CATEGORY = "core"; + public static final String DSPACE_OBJECT = "dSpaceObject"; + public static final String EPERSON = "ePerson"; + + private Integer id; + private String type; + private List subscriptionParameterList = new ArrayList<>(); + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } + + public void setType(String type) { + this.type = type; + } + + public List getSubscriptionParameterList() { + return subscriptionParameterList; + } + + public void setSubscriptionParameterList(List subscriptionParameterList) { + this.subscriptionParameterList = subscriptionParameterList; + } + public String getSubscriptionType() { + return this.type; + } + + @Override + public Integer getId() { + return id; + } + + @Override + public void setId(Integer id) { + this.id = id; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java new file mode 100644 index 000000000000..0f688e1b96d7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java @@ -0,0 +1,22 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * The Resource representation of a Script object + */ +@RelNameDSpaceResource(SubscriptionRest.NAME) +public class SubscriptionResource extends DSpaceResource { + public SubscriptionResource(SubscriptionRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java new file mode 100644 index 000000000000..0c4623f93bfc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -0,0 +1,63 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import org.apache.poi.poifs.crypt.DataSpaceMapUtils; +import org.dspace.app.rest.model.DSpaceObjectRest; +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.service.SubscribeService; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.LazyInitializer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.stereotype.Component; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import javax.transaction.Transactional; +import java.sql.SQLException; + +/** + * Link repository for "mappedCollections" subresource of an individual item. + */ +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.DSPACE_OBJECT) +@Transactional +public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + SubscribeService subscribeService; + + @Autowired + ItemService itemService; + + public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, + Integer subscriptionId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Subscription subscription = subscribeService.findById(context, subscriptionId); + if (subscription == null) { + throw new ResourceNotFoundException("No such subscription: " + subscriptionId); + } + HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); + LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); + + return converter.toRest(initializer.getImplementation(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java new file mode 100644 index 000000000000..6490498a3394 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.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.app.rest.repository; + +import org.dspace.app.rest.model.EPersonRest; +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.core.Context; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.SubscribeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.stereotype.Component; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import java.sql.SQLException; + +/** + * Link repository for "mappedCollections" subresource of an individual item. + */ +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.EPERSON) +public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + SubscribeService subscribeService; + + public EPersonRest getEPerson(@Nullable HttpServletRequest request, + Integer subscriptionId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Subscription subscription = subscribeService.findById(context, subscriptionId); + if (subscription == null) { + throw new ResourceNotFoundException("No such subscription: " + subscriptionId); + } + + return converter.toRest(subscription.getePerson(), projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java new file mode 100644 index 000000000000..fd6812a4ca1a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -0,0 +1,171 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.DSpaceObject; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.SubscribeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +/** + * This is the repository responsible to manage SubscriptionRest object + * + * @author Alba Aliu at atis.al + */ + +@Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME) +public class SubscriptionRestRepository extends DSpaceRestRepository { + private static final Logger log = LogManager.getLogger(); + @Autowired + AuthorizeService authorizeService; + @Autowired + SubscribeService subscribeService; + @Autowired + protected ConverterService converter; + @Autowired + protected EPersonService personService; + @Autowired(required = true) + protected ContentServiceFactory contentServiceFactory; + + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public SubscriptionRest findOne(Context context, Integer id) { + try { + Subscription subscription = subscribeService.findById(context, id); + if (subscription == null) { + throw new ResourceNotFoundException("The subscription for ID: " + id + " could not be found"); + } + return converter.toRest(subscription, utils.obtainProjection()); + } catch (SQLException sqlException) { + throw new RuntimeException(sqlException.getMessage(), sqlException); + } + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + try { + List subscriptionList = subscribeService.findAll(context); + return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); + } catch (SQLException sqlException) { + throw new RuntimeException(sqlException.getMessage(), sqlException); + } + } + + @SearchRestMethod(name = "findByEPerson") + public Page findAllByEPerson(Context context, String id) { + try { + EPerson ePerson = personService.findByNetid(context, id); + if (context.getCurrentUser().equals(ePerson) || authorizeService.isAdmin(context, ePerson)) { + List subscriptionList = subscribeService.getSubscriptions(context, ePerson); + return converter.toRest(subscriptionList, utils.obtainProjection()); + } else { + throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); + } + } catch (SQLException | AuthorizeException sqlException) { + throw new RuntimeException(sqlException.getMessage(), sqlException); + } + } + + @Override + @PreAuthorize("isAuthenticated()") + protected SubscriptionRest createAndReturn(Context context) { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + ObjectMapper mapper = new ObjectMapper(); + SubscriptionRest subscriptionRest = null; + DSpaceObject dSpaceObject = null; + try { + subscriptionRest = mapper.readValue(req.getInputStream(), SubscriptionRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("error parsing the body"); + } + try { +// EPerson ePerson = personService.findByNetid(context, subscriptionRest.getePerson().getNetid()) ; + Subscription subscription = subscribeService.subscribe(context, null, + dSpaceObject, + subscriptionRest.getSubscriptionParameterList(), + subscriptionRest.getType()); + return converter.toRest(subscription, utils.obtainProjection()); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + @PreAuthorize("isAuthenticated()") + protected SubscriptionRest put(Context context, HttpServletRequest request, String apiCategory, String model, + Integer id, JsonNode jsonNode) throws SQLException, AuthorizeException { + SubscriptionRest subscriptionRest = null; + try { + subscriptionRest = new ObjectMapper().readValue(jsonNode.toString(), SubscriptionRest.class); + } catch (IOException e) { + throw new UnprocessableEntityException("Error parsing subscription json: " + e.getMessage()); + } + Subscription subscription = null; + String notFoundException = "ResourceNotFoundException:" + apiCategory + "." + model + + " with id: " + id + " not found"; + try { + subscription = subscribeService.findById(context, id); + if (subscription == null) { + throw new ResourceNotFoundException(notFoundException); + } + } catch (SQLException e) { + throw new ResourceNotFoundException(notFoundException); + } + if (id.equals(subscriptionRest.getId())) { + subscription = subscribeService.updateSubscription(context, subscriptionRest.getId(), null, + null, subscriptionRest.getSubscriptionParameterList(), subscriptionRest.getType()); + return converter.toRest(subscription, utils.obtainProjection()); + } else { + throw new IllegalArgumentException("The id in the Json and the id in the url do not match: " + + id + ", " + + subscriptionRest.getId()); + } + } + + @Override + @PreAuthorize("isAuthenticated()") + public void delete(Context context, Integer id) { + try { + subscribeService.deleteSubscription(context, id); + } catch (SQLException | AuthorizeException sqlException) { + throw new RuntimeException(sqlException.getMessage(), sqlException); + } + } + + @Override + public Class getDomainClass() { + return SubscriptionRest.class; + } + +} diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 085ed0bd6e2c..73002fc6925c 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -66,7 +66,7 @@ - + From 40c0c9b7db8d5b4235374111c6ee7c00c42f73d7 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 1 Dec 2022 18:29:19 +0100 Subject: [PATCH 075/394] [DSC-183] Created Subscription_Parameter, Changed Subscription and created entry points components for Subscription --- .../SubscribeParameterServiceImpl.java | 88 +++ .../dspace/eperson/SubscribeServiceImpl.java | 39 +- .../java/org/dspace/eperson/Subscription.java | 18 +- .../eperson/service/SubscribeService.java | 15 + ...mter_change_columns_subscription_table.sql | 10 +- ...mter_change_columns_subscription_table.sql | 6 +- ...mter_change_columns_subscription_table.sql | 14 +- .../SubscriptionRestRepository.java | 15 + ...earcherProfileVisibleReplaceOperation.java | 2 +- .../SubscriptionParameterAddOperation.java | 86 +++ .../SubscriptionParameterRemoveOperation.java | 68 ++ ...SubscriptionParameterReplaceOperation.java | 108 +++ .../rest/SubscriptionRestRepositoryIT.java | 677 ++++++++++++++++++ 13 files changed, 1121 insertions(+), 25 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java new file mode 100644 index 000000000000..63b7af5ea2ad --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java @@ -0,0 +1,88 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.eperson; + +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.DSpaceObject; +import org.dspace.content.service.CollectionService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.core.LogManager; +import org.dspace.eperson.dao.SubscriptionDAO; +import org.dspace.eperson.dao.SubscriptionParameterDAO; +import org.dspace.eperson.service.SubscribeService; +import org.dspace.eperson.service.SubscriptionParameterService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.sql.SQLException; +import java.util.List; + +/** + * Class defining methods for sending new item e-mail alerts to users + * + * @author Robert Tansley + * @version $Revision$ + */ +public class SubscribeParameterServiceImpl implements SubscriptionParameterService { + /** + * log4j logger + */ + private Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeParameterServiceImpl.class); + + @Autowired(required = true) + protected SubscriptionParameterDAO subscriptionParameterDAO; + + + protected SubscribeParameterServiceImpl() { + + } + + @Override + public List findAll(Context context) throws SQLException { + return subscriptionParameterDAO.findAll(context, SubscriptionParameter.class); + } + + @Override + public SubscriptionParameter add(Context context, String name, String value, Subscription subscription) throws SQLException, AuthorizeException { + SubscriptionParameter subscriptionParameter = subscriptionParameterDAO.create(context, new SubscriptionParameter()); + subscriptionParameter.setName(name); + subscriptionParameter.setSubscription(subscription); + subscriptionParameter.setValue(value); + return subscriptionParameter; + } + @Override + public SubscriptionParameter edit(Context context,Integer id,String value, String name, Subscription subscription) throws SQLException, AuthorizeException { + SubscriptionParameter subscriptionParameter = subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); + subscriptionParameter.setId(id); + subscriptionParameter.setName(name); + subscriptionParameter.setSubscription(subscription); + subscriptionParameter.setValue(value); + subscriptionParameterDAO.save(context, subscriptionParameter); + return subscriptionParameter; + } + + @Override + public SubscriptionParameter findById(Context context, int id) throws SQLException { + return subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); + } + + @Override + public void deleteSubscriptionParameter(Context context, Integer id) throws SQLException, AuthorizeException { + SubscriptionParameter subscriptionParameter = subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); + if (subscriptionParameter != null) { + subscriptionParameter.setSubscription(null); + subscriptionParameterDAO.delete(context, subscriptionParameter); + } else { + throw new SQLException("Subscription parameter with id" + id + "do not exists"); + } + + } +} diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 21890baf9e17..73ee1e4b0551 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -152,18 +152,43 @@ public Subscription updateSubscription(Context context, Integer id, String type) throws SQLException, AuthorizeException { // must be admin or the subscriber of the subscription if (authorizeService.isAdmin(context, context.getCurrentUser()) || eperson.equals(context.getCurrentUser())) { - Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); - subscription.setType(type); - subscription.setdSpaceObject(dSpaceObject); - subscription.setSubscriptionParameterList(subscriptionParameterList); - subscription.setePerson(eperson); - subscriptionDAO.save(context, subscription); - return subscription; + Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); + subscriptionDB.removeParameterList(); + subscriptionDB.setType(type); + subscriptionDB.setdSpaceObject(dSpaceObject); + subscriptionParameterList.forEach(subscriptionParameter -> subscriptionDB.addParameter(subscriptionParameter)); + subscriptionDB.setePerson(eperson); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; } else { throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); } } + @Override + public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { + // must be admin or the subscriber of the subscription + Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); + if (authorizeService.isAdmin(context, context.getCurrentUser()) || subscriptionDB.getePerson().equals(context.getCurrentUser())) { + subscriptionDB.addParameter(subscriptionParameter); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; + } else { + throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); + } + } + @Override + public Subscription removeSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { + // must be admin or the subscriber of the subscription + Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); + if (authorizeService.isAdmin(context, context.getCurrentUser()) || subscriptionDB.getePerson().equals(context.getCurrentUser())) { + subscriptionDB.removeParameter(subscriptionParameter); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; + } else { + throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); + } + } @Override public void deleteSubscription(Context context, Integer id) throws SQLException, AuthorizeException { // initially find the eperson associated with the subscription diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index ed1f50dd94a1..ecd5b6dd1cb0 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -55,8 +55,9 @@ public class Subscription implements ReloadableEntity { @Column(name = "type") private String type; - @OneToMany(fetch = FetchType.EAGER, mappedBy = "subscription", cascade = CascadeType.ALL) + @OneToMany(fetch = FetchType.EAGER, mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true) private List subscriptionParameterList = new ArrayList<>(); + /** * Protected constructor, create object using: * {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, DSpaceObject, List, String)} @@ -105,4 +106,17 @@ public List getSubscriptionParameterList() { public void setSubscriptionParameterList(List subscriptionList) { this.subscriptionParameterList = subscriptionList; } -} + + public void addParameter(SubscriptionParameter subscriptionParameter) { + subscriptionParameterList.add(subscriptionParameter); + subscriptionParameter.setSubscription(this); + } + + public void removeParameterList() { + subscriptionParameterList.clear(); + } + + public void removeParameter(SubscriptionParameter subscriptionParameter) { + subscriptionParameterList.remove(subscriptionParameter); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 5ff92eab5bbf..d1b2b293f571 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -148,6 +148,21 @@ public Subscription updateSubscription(Context context, Integer id, EPerson epe List subscriptionParameterList, String type) throws SQLException, AuthorizeException; + /** + * Deletes a subscription + * + * @param context DSpace context + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; + /** + * Deletes a subscription + * + * @param context DSpace context + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public Subscription removeSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; + /** * Deletes a subscription * diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index 69413b880796..95e46692b112 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -15,20 +15,20 @@ CREATE SEQUENCE subscription_parameter_seq; CREATE TABLE subscription_parameter ( subscription_parameter_id INTEGER NOT NULL, - parameter_name CHARACTER VARYING(255), - parameter_value CHARACTER VARYING(255), + name CHARACTER VARYING(255), + value CHARACTER VARYING(255), subscription_id INTEGER NOT NULL, CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ); -- -- --ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; ----- -- ---ALTER TABLE subscription ALTER COLUMN collection_id RENAME TO dspace_object_id; +-- -- +ALTER TABLE subscription ALTER COLUMN collection_id RENAME TO dspace_object_id; -- -- ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); ---- -- ---ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject(uuid); +ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject(uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index 1fc1935eab0f..d81fbd89b36c 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -25,12 +25,12 @@ CREATE TABLE subscription_parameter -- -- --ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey ------- -- ---ALTER TABLE subscription ALTER COLUMN collection_id RENAME TO dspace_object_id; +---- -- +ALTER TABLE subscription ALTER COLUMN collection_id RENAME TO dspace_object_id; ---- -- ALTER TABLE subscription ADD COL ---- -- ---ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); +ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index bc382613ab54..f4594931fed0 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -21,15 +21,15 @@ CREATE TABLE subscription_parameter CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ); --- -- + -- --ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; ----- -- ---ALTER TABLE subscription RENAME COLUMN collection_id TO dspace_object_id; ----- -- +-- -- +ALTER TABLE subscription RENAME COLUMN collection_id TO dspace_object_id; +-- -- ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); ---- -- ---ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); --- --- +ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- + + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index fd6812a4ca1a..5954cfbb09a7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -153,6 +153,21 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri } } + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public void patch(Context context, HttpServletRequest request, String apiCategory, String model, Integer id, Patch patch) throws UnprocessableEntityException, DSpaceBadRequestException { + Subscription subscription = null; + try { + subscription = subscribeService.findById(context, id); + if (subscription == null) { + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); + } + resourcePatch.patch(context, subscription, patch.getOperations()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override @PreAuthorize("isAuthenticated()") public void delete(Context context, Integer id) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.java index 3e3c0c82f204..5ab8cd88a45b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.java @@ -21,7 +21,7 @@ /** * Implementation for ResearcherProfile visibility patches. - * + *SubscriptionParameterAddOperationpatches * Example: * curl -X PATCH http://${dspace.server.url}/api/eperson/profiles/<:id-eperson> -H " * Content-Type: application/json" -d '[{ "op": "replace", "path": " diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java new file mode 100644 index 000000000000..8b3e9ede93eb --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java @@ -0,0 +1,86 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository.patch.operation; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.app.profile.service.ResearcherProfileService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.RESTAuthorizationException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SubscriptionParameterRest; +import org.dspace.app.rest.model.patch.JsonValueEvaluator; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.service.SubscribeService; +import org.dspace.eperson.service.SubscriptionParameterService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.sql.SQLException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Implementation for SubscriptionParameterAddOperation patches. + *

+ * Example: + * curl -X PATCH http://${dspace.server.url}api/core/subscriptions/<:id-subscription> -H " + * Content-Type: application/json" -d '[{ "op": "replace", "path": " + * + */ +@Component +public class SubscriptionParameterAddOperation extends PatchOperation { + + @Autowired + private SubscribeService subscribeService; + + + @Override + public Subscription perform(Context context, Subscription subscription, Operation operation) + throws SQLException { + if (supports(subscription, operation)) { + JsonNode value = null; + ObjectMapper objectMapper = new ObjectMapper(); + try { + if (operation.getValue() instanceof JsonValueEvaluator) { + value = ((JsonValueEvaluator) operation.getValue()).getValueNode(); + } else { + value = objectMapper.readTree((String) operation.getValue()); + } + SubscriptionParameterRest subscriptionParameterRest = objectMapper.readValue(value.toString(), SubscriptionParameterRest.class); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setSubscription(subscription); + subscriptionParameter.setValue(subscriptionParameterRest.getValue()); + subscriptionParameter.setName(subscriptionParameterRest.getName()); + subscribeService.addSubscriptionParameter(context, subscription.getID(), subscriptionParameter); + } catch (UnprocessableEntityException e) { + throw new UnprocessableEntityException(e.getMessage(), e); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } else { + throw new DSpaceBadRequestException("Subscription does not support this operation"); + } + return subscription; + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof Subscription + && operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD)); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java new file mode 100644 index 000000000000..33e81dac3c56 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository.patch.operation; + +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.app.profile.service.ResearcherProfileService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.RESTAuthorizationException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.service.SubscribeService; +import org.dspace.eperson.service.SubscriptionParameterService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.sql.SQLException; + +/** + * Implementation for SubscriptionParameterRemoveOperation patches. + *

+ * Example: + * curl -X PATCH http://${dspace.server.url}api/core/subscriptions/<:id-subscription> -H " + * Content-Type: application/json" -d '[{ "op": "replace", "path": " + * + */ +@Component +public class SubscriptionParameterRemoveOperation extends PatchOperation { + + @Autowired + private SubscriptionParameterService subscriptionParameterService; + @Autowired + private SubscribeService subscribeService; + + + @Override + public Subscription perform(Context context, Subscription subscription, Operation operation) + throws SQLException { + if (supports(subscription, operation)) { + Integer path = Integer.parseInt(operation.getPath().split("/")[2]); + try { + SubscriptionParameter subscriptionParameter = subscriptionParameterService.findById(context, path); + subscribeService.removeSubscriptionParameter(context, subscription.getID(), subscriptionParameter); + } catch (AuthorizeException e) { + throw new RESTAuthorizationException("Unauthorized user for removing subscription parameter"); + } + } else { + throw new DSpaceBadRequestException("Subscription does not support this operation"); + + } + return subscription; + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof Subscription + && operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE)); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java new file mode 100644 index 000000000000..1ec599d75922 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java @@ -0,0 +1,108 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository.patch.operation; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.jsonldjava.utils.Obj; +import javassist.NotFoundException; +import org.apache.xpath.operations.Bool; +import org.dspace.app.profile.ResearcherProfile; +import org.dspace.app.profile.service.ResearcherProfileService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.RESTAuthorizationException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SubscriptionParameterRest; +import org.dspace.app.rest.model.patch.JsonValueEvaluator; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.service.SubscriptionParameterService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation for SubscriptionParameterReplaceOperation patches. + *

+ * Example: + * curl -X PATCH http://${dspace.server.url}api/core/subscriptions/<:id-subscription> -H " + * Content-Type: application/json" -d '[{ "op": "replace", "path": " + * + */ +@Component +public class SubscriptionParameterReplaceOperation extends PatchOperation { + + @Autowired + private SubscriptionParameterService subscriptionParameterService; + + + @Override + public Subscription perform(Context context, Subscription subscription, Operation operation) + throws SQLException { + if (supports(subscription, operation)) { + Integer subscriptionParameterId = Integer.parseInt(operation.getPath().split("/", 3)[2]); + checkModelForExistingValue(subscription, subscriptionParameterId); + JsonNode value = null; + ObjectMapper objectMapper = new ObjectMapper(); + try { + if (operation.getValue() instanceof JsonValueEvaluator) { + value = ((JsonValueEvaluator) operation.getValue()).getValueNode(); + } else { + value = objectMapper.readTree((String) operation.getValue()); + } + SubscriptionParameterRest subscriptionParameterRest = objectMapper.readValue(value.toString(), SubscriptionParameterRest.class); + try { + SubscriptionParameter subscriptionParameter = subscriptionParameterService.edit(context, subscriptionParameterId, subscriptionParameterRest.getValue(), + subscriptionParameterRest.getName(), + subscription); + } catch (SQLException | AuthorizeException exception) { + throw new RuntimeException(exception); + } + } catch (UnprocessableEntityException e) { + throw new UnprocessableEntityException(e.getMessage(), e); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + return subscription; + } else { + throw new DSpaceBadRequestException("Subscription does not support this operation"); + } + } + + @Override + public boolean supports(Object objectToMatch, Operation operation) { + return (objectToMatch instanceof Subscription + && operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE)); + } + + /** + * Checks whether the subscription + * + * @param subscription Object on which patch is being done + */ + private void checkModelForExistingValue(Subscription subscription, Integer id) { + subscription.getSubscriptionParameterList().stream().filter(subscriptionParameter -> { + return subscriptionParameter.getId().equals(id); + }).findFirst().orElseThrow(); + + } + + public SubscriptionParameter generateObjectFromValue(Subscription subscription, String name) { + return subscription + .getSubscriptionParameterList().stream().filter(subscriptionParameter1 -> { + return subscriptionParameter1.getName().equals(name); + }).findFirst().orElseThrow(); + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java new file mode 100644 index 000000000000..b9666f18a53c --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -0,0 +1,677 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import org.dspace.app.rest.model.patch.AddOperation; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.RemoveOperation; +import org.dspace.app.rest.model.patch.ReplaceOperation; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.SiteService; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.service.SubscribeService; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Integration test to test the /api/config/submissionforms endpoint + * (Class has to start or end with IT to be picked up by the failsafe plugin) + */ +public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + SubscribeService subscribeService; + + @Autowired + SiteService siteService; + + @Test + public void findAll() throws Exception { + context.turnOffAuthorisationSystem(); + //When we call the root endpoint as anonymous user + getClient().perform(get("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isUnauthorized()); + + String token = getAuthToken(admin.getEmail(), password); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Frequency"); + subscriptionParameter.setValue("Daily"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, admin, col1, subscriptionParameterList, "TypeTest"); + subscriptionParameter.setSubscription(subscription); + //When we call the root endpoint + context.restoreAuthSystemState(); + getClient(token).perform(get("/api/core/subscriptions")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))) +// .andExpect(jsonPath("$._embedded.subscriptions[0].type", is("TypeTest"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))); + } + @Test + public void findByIdAsAdministrator() throws Exception { + //When we call the root endpoint as anonymous user + getClient().perform(get("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isUnauthorized()); + + String token = getAuthToken(admin.getEmail(), password); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, admin, publicItem1, subscriptionParameterList, "TestType"); + context.restoreAuthSystemState(); + //When we call the root endpoint + getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.type", is("TestType"))) + .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + + + } + @Test + public void findByIdAsRandomUser() throws Exception { + //When we call the root endpoint as anonymous user + getClient().perform(get("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isUnauthorized()); + + String token = getAuthToken(eperson.getEmail(), password); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, admin, publicItem1, subscriptionParameterList, "TestType"); + context.restoreAuthSystemState(); + //When we call the root endpoint + getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) + //The status has to be 200 OK + .andExpect(status().isUnauthorized()); + } + @Test + public void findAllSubscriptionsByEPerson() throws Exception { + //When we call the root endpoint as anonymous user + getClient().perform(get("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isUnauthorized()); + String token = getAuthToken(eperson.getEmail(), password); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter1"); + subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "TestType"); + context.restoreAuthSystemState(); + //When we call the root endpoint + getClient(token).perform(get("/api/core/subscriptions/findByEPerson?id=" + eperson.getID())) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.type", is("TestType"))) + .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter1"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter1"))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + EPerson epersonIT = EPersonBuilder.createEPerson(context) + .withEmail("epersonIT@example.com") + .withPassword(password) + .withLanguage("al") + .build(); + String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); + getClient(epersonITtoken).perform(get("/api/core/subscriptions/" + subscription.getID())) + //The status has to be 200 OK + .andExpect(status().isUnauthorized()); + } + @Test + public void addSubscriptionNotLoggedIn() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter1"); + subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "TestType"); + context.restoreAuthSystemState(); + //When we call the root endpoint as anonymous user + getClient().perform(post("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isUnauthorized()); + } + @Test + public void addSubscriptionAsLoggedIn() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Test"); + subscriptionParameter.setValue("Test"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Type"); + context.restoreAuthSystemState(); + //When we call the root endpoint as anonymous user + getClient().perform(post("/api/core/subscriptions")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.type", is("Type"))) + .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Test"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("Test"))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + } + @Test + public void editSubscriptionAnonymous() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter1"); + subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "TestType"); + context.restoreAuthSystemState(); + //When we call the root endpoint as anonymous user + getClient().perform(put("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isUnauthorized()); + } + @Test + public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson epersonIT = EPersonBuilder.createEPerson(context) + .withEmail("epersonIT@example.com") + .withPassword(password) + .withLanguage("al") + .build(); + String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter1"); + subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "TestType"); + context.restoreAuthSystemState(); + //When we call the root endpoint as anonymous user + getClient(epersonITtoken).perform(put("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isUnauthorized()); + } + @Test + public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Frequency"); + subscriptionParameter.setValue("Daily"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + context.restoreAuthSystemState(); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isOk()); + //When we call the root endpoint as anonymous user + getClient(tokenAdmin).perform(put("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.type", is("Test"))) + .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("Daily"))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + } + @Test + public void deleteSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson epersonIT = EPersonBuilder.createEPerson(context) + .withEmail("epersonIT@example.com") + .withPassword(password) + .withLanguage("al") + .build(); + String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Frequency"); + subscriptionParameter.setValue("Daily"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + context.restoreAuthSystemState(); + getClient(epersonITtoken).perform(put("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isUnauthorized()); + } + @Test + public void deleteSubscriptionAsAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Frequency"); + subscriptionParameter.setValue("Daily"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + context.restoreAuthSystemState(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(put("/api/core/subscriptions")) + //The status has to be 403 Not Authorized + .andExpect(status().isOk()); + } + @Test + public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("TestName"); + subscriptionParameter.setValue("TestValue"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + String token = getAuthToken(admin.getEmail(), password); + List ops = new ArrayList(); + Map value = new HashMap<>(); + value.put("name", "frequency"); + value.put("value", "monthly"); + ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/"+subscription.getSubscriptionParameterList().get(0).getId(), value); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + getClient(token).perform(patch("/api/core/subscriptions/"+subscription.getID()) + .content(patchBody) + ) + //The status has to be 403 Not Authorized + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.type", is("Test"))) + .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("monthly"))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + } + @Test + public void patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Exception { + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("TestName"); + subscriptionParameter.setValue("TestValue"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + List ops = new ArrayList(); + Map value = new HashMap<>(); + value.put("name", "frequency"); + value.put("value", "monthly"); + ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/"+subscription.getSubscriptionParameterList().get(0).getId(), value); + ops.add(replaceOperation); + String patchBody = getPatchContent(ops); + EPerson epersonIT = EPersonBuilder.createEPerson(context) + .withEmail("epersonIT@example.com") + .withPassword(password) + .withLanguage("al") + .build(); + String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); + getClient(epersonITtoken).perform(patch("/api/core/subscriptions/"+subscription.getID()) + .content(patchBody) + ) + //The status has to be 200 OK + .andExpect(status().isUnauthorized()); + } + @Test + public void patchAddSubscriptionParameter() throws Exception { + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("TestName"); + subscriptionParameter.setValue("TestValue"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + String token = getAuthToken(admin.getEmail(), password); + List ops = new ArrayList(); + Map value = new HashMap<>(); + value.put("name", "frequency"); + value.put("value", "monthly"); + AddOperation addOperation = new AddOperation("/subscriptionsParameter/"+subscription.getSubscriptionParameterList().get(0).getId(), value); + ops.add(addOperation); + String patchBody = getPatchContent(ops); + getClient(token).perform(patch("/api/core/subscriptions/"+subscription.getID()) + .content(patchBody) + ) + //The status has to be 403 Not Authorized + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.type", is("Test"))) + .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("TestName"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("TestValue"))) + .andExpect(jsonPath("$.subscriptionParameterList[1].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[1].value", is("monthly"))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + } + @Test + public void patchRemoveSubscriptionParameter() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + // creation of the item which will be the DSO related with a subscription + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Item") + .withIssueDate("2020-10-17") + .withAuthor("John, Doe") + .withSubject("Test") + .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("TestName"); + subscriptionParameter.setValue("TestValue"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + String token = getAuthToken(admin.getEmail(), password); + List ops = new ArrayList(); + Map value = new HashMap<>(); + value.put("name", "frequency"); + value.put("value", "monthly"); + RemoveOperation removeOperation = new RemoveOperation("/subscriptionsParameter/"+subscription.getSubscriptionParameterList().get(0).getId()); + ops.add(removeOperation); + String patchBody = getPatchContent(ops); + context.restoreAuthSystemState(); + getClient(token).perform(patch("/api/core/subscriptions/"+subscription.getID()) + .content(patchBody) + ) + //The status has to be 403 Not Authorized + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.type", is("Test"))) + .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$.subscriptionParameterList", Matchers.arrayWithSize(0))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))); + } +} \ No newline at end of file From b0362625149d33dddb9f851bd0a3ed90a1c8d695 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 1 Dec 2022 18:32:11 +0100 Subject: [PATCH 076/394] [DSC-183] fixes --- .../SubscribeParameterServiceImpl.java | 13 +-- .../dspace/eperson/SubscribeServiceImpl.java | 5 +- .../java/org/dspace/eperson/Subscription.java | 2 +- .../eperson/service/SubscribeService.java | 15 +++- .../service/SubscriptionParameterService.java | 86 +++++++++++++++++++ ...mter_change_columns_subscription_table.sql | 12 +-- ...mter_change_columns_subscription_table.sql | 11 +-- ...mter_change_columns_subscription_table.sql | 6 +- .../rest/converter/SubscriptionConverter.java | 4 +- .../app/rest/model/SubscriptionRest.java | 2 - .../model/hateoas/SubscriptionResource.java | 2 +- ...ubscriptionDSpaceObjectLinkRepository.java | 8 +- .../SubscriptionEPersonLinkRepository.java | 10 +-- .../SubscriptionRestRepository.java | 43 +++++++--- .../SubscriptionParameterAddOperation.java | 10 --- .../SubscriptionParameterRemoveOperation.java | 4 +- ...SubscriptionParameterReplaceOperation.java | 17 +--- .../rest/SubscriptionRestRepositoryIT.java | 35 ++++---- 18 files changed, 179 insertions(+), 106 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java index 63b7af5ea2ad..2bb2133dceba 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java @@ -9,16 +9,8 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.Collection; -import org.dspace.content.DSpaceObject; -import org.dspace.content.service.CollectionService; -import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.LogManager; -import org.dspace.eperson.dao.SubscriptionDAO; import org.dspace.eperson.dao.SubscriptionParameterDAO; -import org.dspace.eperson.service.SubscribeService; import org.dspace.eperson.service.SubscriptionParameterService; import org.springframework.beans.factory.annotation.Autowired; @@ -26,10 +18,9 @@ import java.util.List; /** - * Class defining methods for sending new item e-mail alerts to users + * Class implemennting method for service layer of SubscriptionParameter entity * - * @author Robert Tansley - * @version $Revision$ + * @author Alba Aliu at atis.al */ public class SubscribeParameterServiceImpl implements SubscriptionParameterService { /** diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 73ee1e4b0551..a6f277f0a30f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -62,7 +62,8 @@ public Subscription subscribe(Context context, EPerson eperson, .getCurrentUser().getID().equals(eperson.getID())))) { if (!isSubscribed(context, eperson, dSpaceObject)) { Subscription new_subscription = subscriptionDAO.create(context, new Subscription()); - new_subscription.setSubscriptionParameterList(subscriptionParameterList); + subscriptionParameterList.forEach(subscriptionParameter -> new_subscription.addParameter(subscriptionParameter)); +// new_subscription.setSubscriptionParameterList(subscriptionParameterList); new_subscription.setePerson(eperson); new_subscription.setdSpaceObject(dSpaceObject); new_subscription.setType(type); @@ -100,7 +101,7 @@ public void unsubscribe(Context context, EPerson eperson, } @Override - public List getSubscriptions(Context context, EPerson eperson) + public List getSubscriptionsByEPerson(Context context, EPerson eperson) throws SQLException { return subscriptionDAO.findByEPerson(context, eperson); } diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index ecd5b6dd1cb0..0824e9fdd66f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -55,7 +55,7 @@ public class Subscription implements ReloadableEntity { @Column(name = "type") private String type; - @OneToMany(fetch = FetchType.EAGER, mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(fetch = FetchType.LAZY, mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true) private List subscriptionParameterList = new ArrayList<>(); /** diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index d1b2b293f571..c137fe2e7bc9 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -75,7 +75,7 @@ public void unsubscribe(Context context, EPerson eperson, * @return array of collections e-person is subscribed to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getSubscriptions(Context context, EPerson eperson) throws SQLException; + public List getSubscriptionsByEPerson(Context context, EPerson eperson) throws SQLException; /** * Find out which collections the currently logged in e-person can subscribe to @@ -141,6 +141,11 @@ public boolean isSubscribed(Context context, EPerson eperson, * Updates a subscription by id * * @param context DSpace context + * @param id Integer id + * @param eperson EPerson eperson + * @param dSpaceObject DSpaceObject dSpaceObject + * @param subscriptionParameterList List subscriptionParameterList + * @param type String type * @throws SQLException An exception that provides information on a database access error or other errors. */ public Subscription updateSubscription(Context context, Integer id, EPerson eperson, @@ -149,16 +154,20 @@ public Subscription updateSubscription(Context context, Integer id, EPerson epe String type) throws SQLException, AuthorizeException; /** - * Deletes a subscription + * Adds a parameter to a subscription * * @param context DSpace context + * @param id Integer id + * @param subscriptionParameter SubscriptionParameter subscriptionParameter * @throws SQLException An exception that provides information on a database access error or other errors. */ public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; /** - * Deletes a subscription + * Deletes a parameter from subscription * * @param context DSpace context + * @param id Integer id + * @param subscriptionParameter SubscriptionParameter subscriptionParameter * @throws SQLException An exception that provides information on a database access error or other errors. */ public Subscription removeSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java new file mode 100644 index 000000000000..1870457437dc --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java @@ -0,0 +1,86 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.eperson.service; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; + +import java.sql.SQLException; +import java.util.List; + +/** + * Service interface class for the SubscriptionParameter object. + * The implementation of this class is responsible for all business logic calls for the SubscriptionParameter object and is + * autowired by spring + * Class defining methods for sending new item e-mail alerts to users + * + * @author Alba Aliu @atis.al + */ +public interface SubscriptionParameterService { + /** + * Finds all of subscriptions parameter + * + * @param context DSpace context + * @return list of Subscription objects + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public List findAll(Context context) throws SQLException; + + /** + * Adds a new subscription parameter related with a subscription + * + * @param context DSpace context + * @param value String value + * @param name String name + * @param subscription Subscription subscription + * @throws SQLException An exception that provides information on a database access error or other errors. + * @throws AuthorizeException Exception indicating the current user of the context does not have permission + * to perform a particular action. + */ + public SubscriptionParameter add(Context context, String value, + String name, + Subscription subscription) throws SQLException, AuthorizeException; + + /** + * Updates a subscription parameter with id + * + * @param context DSpace context + * @param id Integer id + * @param value String value + * @param name String name + * @param subscription Subscription subscription + * @throws SQLException An exception that provides information on a database access error or other errors. + * @throws AuthorizeException Exception indicating the current user of the context does not have permission + * to perform a particular action. + */ + public SubscriptionParameter edit(Context context, Integer id, String value, + String name, + Subscription subscription) throws SQLException, AuthorizeException; + + /** + * Finds a subscriptionParameter by id + * + * @param context DSpace context + * @param id the id of subscriptionParameter to be searched + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public SubscriptionParameter findById(Context context, int id) throws SQLException; + + + /** + * Deletes a subscriptionParameter with id + * + * @param context DSpace context + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public void deleteSubscriptionParameter(Context context, Integer id) throws SQLException, AuthorizeException; + + +} diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index 95e46692b112..b37823c0fa04 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -22,12 +22,14 @@ CREATE TABLE subscription_parameter CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ); -- -- ---ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; --- -- -ALTER TABLE subscription ALTER COLUMN collection_id RENAME TO dspace_object_id; --- -- +ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; +-- +ALTER TABLE subscription DROP COLUMN collection_id; +-- +ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; +-- ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); ----- -- +-- ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject(uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index d81fbd89b36c..e668d9d7e569 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -23,13 +23,14 @@ CREATE TABLE subscription_parameter REFERENCES subscription (subscription_id) ); -- -- - ---ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey ----- -- -ALTER TABLE subscription ALTER COLUMN collection_id RENAME TO dspace_object_id; +ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey ---- -- -ALTER TABLE subscription ADD COL +ALTER TABLE subscription DROP COLUMN collection_id; +-- +ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; ---- -- +ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); + ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index f4594931fed0..ee5cd1317d99 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -22,9 +22,11 @@ CREATE TABLE subscription_parameter CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ); -- ---ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; +ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; -- -- -ALTER TABLE subscription RENAME COLUMN collection_id TO dspace_object_id; +ALTER TABLE subscription DROP COLUMN collection_id; +-- +ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; -- -- ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); ---- -- diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java index 6673f00cb129..5198c622bbbc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java @@ -15,9 +15,9 @@ import org.springframework.stereotype.Component; /** - * This is the converter from Entity CrisLayoutTab to the REST data model + * This is the converter from Entity Subscription to the REST data model * - * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Alba Aliu at atis.al * */ @Component diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index 5bd9f074b475..ff36755065aa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -9,8 +9,6 @@ package org.dspace.app.rest.model; import org.dspace.app.rest.RestResourceController; -import org.dspace.eperson.Subscription; -import org.dspace.eperson.SubscriptionParameter; import java.util.ArrayList; import java.util.List; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java index 0f688e1b96d7..2e7ea1618a7a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubscriptionResource.java @@ -12,7 +12,7 @@ import org.dspace.app.rest.utils.Utils; /** - * The Resource representation of a Script object + * The Resource representation of a Subscription object */ @RelNameDSpaceResource(SubscriptionRest.NAME) public class SubscriptionResource extends DSpaceResource { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index 0c4623f93bfc..77279495f035 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -7,12 +7,9 @@ */ package org.dspace.app.rest.repository; -import org.apache.poi.poifs.crypt.DataSpaceMapUtils; import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; -import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.eperson.Subscription; @@ -26,11 +23,10 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; -import javax.transaction.Transactional; import java.sql.SQLException; /** - * Link repository for "mappedCollections" subresource of an individual item. + * Link repository for "dataSpaceObject" of subscription */ @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.DSPACE_OBJECT) @Transactional @@ -39,8 +35,6 @@ public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRe @Autowired SubscribeService subscribeService; - @Autowired - ItemService itemService; public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, Integer subscriptionId, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index 6490498a3394..dfe35e36e268 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -7,24 +7,24 @@ */ package org.dspace.app.rest.repository; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import java.sql.SQLException; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; import org.dspace.core.Context; import org.dspace.eperson.Subscription; -import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.SubscribeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.stereotype.Component; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import java.sql.SQLException; + /** - * Link repository for "mappedCollections" subresource of an individual item. + * Link repository for "eperson" of subscription */ @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.EPERSON) public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 5954cfbb09a7..2f3dbd72523f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -18,12 +18,12 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; -import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.SubscribeService; +import org.dspace.eperson.service.SubscriptionParameterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -53,8 +53,10 @@ public class SubscriptionRestRepository extends DSpaceRestRepository resourcePatch; @Override @@ -85,10 +87,11 @@ public Page findAll(Context context, Pageable pageable) { @SearchRestMethod(name = "findByEPerson") public Page findAllByEPerson(Context context, String id) { try { - EPerson ePerson = personService.findByNetid(context, id); + Context context = obtainContext(); + EPerson ePerson = personService.findByIdOrLegacyId(context, id); if (context.getCurrentUser().equals(ePerson) || authorizeService.isAdmin(context, ePerson)) { - List subscriptionList = subscribeService.getSubscriptions(context, ePerson); - return converter.toRest(subscriptionList, utils.obtainProjection()); + List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, ePerson); + return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); } else { throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); } @@ -110,11 +113,24 @@ protected SubscriptionRest createAndReturn(Context context) { throw new UnprocessableEntityException("error parsing the body"); } try { -// EPerson ePerson = personService.findByNetid(context, subscriptionRest.getePerson().getNetid()) ; - Subscription subscription = subscribeService.subscribe(context, null, - dSpaceObject, - subscriptionRest.getSubscriptionParameterList(), - subscriptionRest.getType()); + Subscription subscription = null; + DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); + EPerson ePerson = personService.findByIdOrLegacyId(context, epersonId); + List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); + if (subscriptionParameterList != null) { + List subscriptionParameters = new ArrayList<>(); + for (SubscriptionParameterRest subscriptionParameterRest : subscriptionParameterList) { + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName(subscriptionParameterRest.getName()); + subscriptionParameter.setValue(subscriptionParameterRest.getValue()); + subscriptionParameters.add(subscriptionParameter); + } + subscription = subscribeService.subscribe(context, ePerson, + dSpaceObject, + subscriptionParameters, + subscriptionRest.getType()); + } + context.commit(); return converter.toRest(subscription, utils.obtainProjection()); } catch (SQLException | AuthorizeException e) { throw new RuntimeException(e.getMessage(), e); @@ -154,8 +170,9 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri } @Override - @PreAuthorize("hasAuthority('ADMIN')") - public void patch(Context context, HttpServletRequest request, String apiCategory, String model, Integer id, Patch patch) throws UnprocessableEntityException, DSpaceBadRequestException { + @PreAuthorize("isAuthenticated()") + public void patch(Context context, HttpServletRequest request, String apiCategory, String model, Integer id, Patch patch) + throws UnprocessableEntityException, DSpaceBadRequestException { Subscription subscription = null; try { subscription = subscribeService.findById(context, id); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java index 8b3e9ede93eb..4ea712c48a1c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java @@ -7,31 +7,21 @@ */ package org.dspace.app.rest.repository.patch.operation; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Lists; -import org.dspace.app.profile.ResearcherProfile; -import org.dspace.app.profile.service.ResearcherProfileService; import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.patch.JsonValueEvaluator; import org.dspace.app.rest.model.patch.Operation; -import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; import org.dspace.eperson.service.SubscribeService; -import org.dspace.eperson.service.SubscriptionParameterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.sql.SQLException; -import java.util.Iterator; -import java.util.List; -import java.util.Map; /** * Implementation for SubscriptionParameterAddOperation patches. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java index 33e81dac3c56..de665c4cb2ec 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java @@ -7,11 +7,9 @@ */ package org.dspace.app.rest.repository.patch.operation; -import org.dspace.app.profile.ResearcherProfile; -import org.dspace.app.profile.service.ResearcherProfileService; + import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RESTAuthorizationException; -import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.patch.Operation; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java index 1ec599d75922..570c35e772a4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java @@ -9,20 +9,13 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.jsonldjava.utils.Obj; -import javassist.NotFoundException; -import org.apache.xpath.operations.Bool; -import org.dspace.app.profile.ResearcherProfile; -import org.dspace.app.profile.service.ResearcherProfileService; import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.patch.JsonValueEvaluator; import org.dspace.app.rest.model.patch.Operation; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; -import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; import org.dspace.eperson.service.SubscriptionParameterService; @@ -30,8 +23,7 @@ import org.springframework.stereotype.Component; import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; + /** * Implementation for SubscriptionParameterReplaceOperation patches. @@ -98,11 +90,4 @@ private void checkModelForExistingValue(Subscription subscription, Integer id) { }).findFirst().orElseThrow(); } - - public SubscriptionParameter generateObjectFromValue(Subscription subscription, String name) { - return subscription - .getSubscriptionParameterList().stream().filter(subscriptionParameter1 -> { - return subscriptionParameter1.getName().equals(name); - }).findFirst().orElseThrow(); - } } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index b9666f18a53c..0a2f606d0ba0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -26,6 +26,7 @@ import org.dspace.eperson.SubscriptionParameter; import org.dspace.eperson.service.SubscribeService; import org.hamcrest.Matchers; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -46,23 +47,32 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** - * Integration test to test the /api/config/submissionforms endpoint + * Integration test to test the /api/config/subscriptions endpoint * (Class has to start or end with IT to be picked up by the failsafe plugin) */ public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired SubscribeService subscribeService; - - @Autowired - SiteService siteService; + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + // We turn off the authorization system in order to create the structure as + // defined below +// context.turnOffAuthorisationSystem(); +// parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); +// colPeople = CollectionBuilder.createCollection(context, parentCommunity).withName("People") +// .withEntityType("Person").build(); +// context.restoreAuthSystemState(); + } @Test public void findAll() throws Exception { context.turnOffAuthorisationSystem(); //When we call the root endpoint as anonymous user getClient().perform(get("/api/core/subscriptions")) - //The status has to be 403 Not Authorized + //The status has to be 401 Not Authorized .andExpect(status().isUnauthorized()); String token = getAuthToken(admin.getEmail(), password); @@ -85,7 +95,7 @@ public void findAll() throws Exception { subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, admin, col1, subscriptionParameterList, "TypeTest"); + Subscription subscription = subscribeService.subscribe(context, admin, publicItem1, subscriptionParameterList, "TypeTest"); subscriptionParameter.setSubscription(subscription); //When we call the root endpoint context.restoreAuthSystemState(); @@ -95,18 +105,7 @@ public void findAll() throws Exception { //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.number", is(0))) -// .andExpect(jsonPath("$._embedded.subscriptions[0].type", is("TypeTest"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))); + .andExpect(jsonPath("$.page.size", is(20))); } @Test public void findByIdAsAdministrator() throws Exception { From bdb4bd53dc12c2f6bacbe51efc62dc8c02666024 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 1 Dec 2022 18:43:42 +0100 Subject: [PATCH 077/394] [DSC-183] New endpoint, IT tests changed --- .../dspace/content/CollectionServiceImpl.java | 2 +- .../dspace/content/CommunityServiceImpl.java | 6 +- .../org/dspace/content/ItemServiceImpl.java | 7 +- .../dspace/eperson/SubscribeServiceImpl.java | 34 ++-- .../dspace/eperson/dao/SubscriptionDAO.java | 5 +- .../eperson/dao/impl/SubscriptionDAOImpl.java | 19 +- .../eperson/service/SubscribeService.java | 14 +- ...mter_change_columns_subscription_table.sql | 10 +- ...mter_change_columns_subscription_table.sql | 7 +- ...mter_change_columns_subscription_table.sql | 5 +- .../SubscriptionRestRepository.java | 28 ++- .../rest/SubscriptionRestRepositoryIT.java | 172 +++++++++++------- 12 files changed, 201 insertions(+), 108 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index e54f609389d8..544db4477a52 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -735,7 +735,7 @@ public void delete(Context context, Collection collection) throws SQLException, collection.getID(), collection.getHandle(), getIdentifiers(context, collection))); // remove subscriptions - hmm, should this be in Subscription.java? - subscribeService.deleteByCollection(context, collection); + subscribeService.deleteByDspaceObject(context, collection); // Remove Template Item removeTemplateItem(context, collection); diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index d0c414eba2d6..6ff1784ac219 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -36,6 +36,7 @@ import org.dspace.core.LogHelper; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; +import org.dspace.eperson.service.SubscribeService; import org.dspace.event.Event; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.IdentifierService; @@ -73,7 +74,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp protected SiteService siteService; @Autowired(required = true) protected IdentifierService identifierService; - + @Autowired(required = true) + protected SubscribeService subscribeService; protected CommunityServiceImpl() { super(); @@ -225,7 +227,7 @@ public Bitstream setLogo(Context context, Community community, InputStream is) context, community, Constants.DELETE))) { canEdit(context, community); } - + subscribeService.deleteByDspaceObject(context, community); // First, delete any existing logo Bitstream oldLogo = community.getLogo(); if (oldLogo != null) { diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 27d0ba189cbd..169308a815e3 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -53,6 +53,7 @@ import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.eperson.service.SubscribeService; import org.dspace.event.Event; import org.dspace.harvest.HarvestedItem; import org.dspace.harvest.service.HarvestedItemService; @@ -149,6 +150,9 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) private ResearcherProfileService researcherProfileService; + @Autowired(required = true) + protected SubscribeService subscribeService; + protected ItemServiceImpl() { super(); } @@ -755,7 +759,8 @@ protected void rawDelete(Context context, Item item) throws AuthorizeException, log.info(LogHelper.getHeader(context, "delete_item", "item_id=" + item.getID())); - + //remove subscription related with it + subscribeService.deleteByDspaceObject(context, item); // Remove relationships for (Relationship relationship : relationshipService.findByItem(context, item, -1, -1, false, false)) { relationshipService.forceDelete(context, relationship, false, false); diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index a6f277f0a30f..5f9ffdb6c550 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -60,17 +60,13 @@ public Subscription subscribe(Context context, EPerson eperson, if (authorizeService.isAdmin(context) || ((context.getCurrentUser() != null) && (context .getCurrentUser().getID().equals(eperson.getID())))) { - if (!isSubscribed(context, eperson, dSpaceObject)) { - Subscription new_subscription = subscriptionDAO.create(context, new Subscription()); - subscriptionParameterList.forEach(subscriptionParameter -> new_subscription.addParameter(subscriptionParameter)); + Subscription new_subscription = subscriptionDAO.create(context, new Subscription()); + subscriptionParameterList.forEach(subscriptionParameter -> new_subscription.addParameter(subscriptionParameter)); // new_subscription.setSubscriptionParameterList(subscriptionParameterList); - new_subscription.setePerson(eperson); - new_subscription.setdSpaceObject(dSpaceObject); - new_subscription.setType(type); - return new_subscription; - } else { - throw new IllegalArgumentException("Subscription already exists"); - } + new_subscription.setePerson(eperson); + new_subscription.setdSpaceObject(dSpaceObject); + new_subscription.setType(type); + return new_subscription; } else { throw new AuthorizeException( "Only admin or e-person themselves can subscribe"); @@ -106,6 +102,12 @@ public List getSubscriptionsByEPerson(Context context, EPerson epe return subscriptionDAO.findByEPerson(context, eperson); } + @Override + public List getSubscriptionsByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject) + throws SQLException { + return subscriptionDAO.findByEPerson(context, eperson); + } + @Override public List getAvailableSubscriptions(Context context) throws SQLException { @@ -127,12 +129,12 @@ public List getAvailableSubscriptions(Context context, EPerson epers @Override public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException { - return subscriptionDAO.findByCollectionAndEPerson(context, eperson, dSpaceObject) != null; + return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject) != null; } @Override - public void deleteByCollection(Context context, Collection collection) throws SQLException { - subscriptionDAO.deleteByCollection(context, collection); + public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException { + subscriptionDAO.deleteByDspaceObject(context, dSpaceObject); } @Override @@ -167,7 +169,7 @@ public Subscription updateSubscription(Context context, Integer id, } @Override - public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { + public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { // must be admin or the subscriber of the subscription Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); if (authorizeService.isAdmin(context, context.getCurrentUser()) || subscriptionDB.getePerson().equals(context.getCurrentUser())) { @@ -178,8 +180,9 @@ public Subscription addSubscriptionParameter(Context context, Integer id, Subscr throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); } } + @Override - public Subscription removeSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { + public Subscription removeSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { // must be admin or the subscriber of the subscription Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); if (authorizeService.isAdmin(context, context.getCurrentUser()) || subscriptionDB.getePerson().equals(context.getCurrentUser())) { @@ -190,6 +193,7 @@ public Subscription removeSubscriptionParameter(Context context, Integer id, Sub throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); } } + @Override public void deleteSubscription(Context context, Integer id) throws SQLException, AuthorizeException { // initially find the eperson associated with the subscription diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index 2547657ef6cd..81b89aab894b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -27,12 +27,11 @@ */ public interface SubscriptionDAO extends GenericDAO { - public void deleteByCollection(Context context, Collection collection) throws SQLException; + public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException; public List findByEPerson(Context context, EPerson eperson) throws SQLException; - public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, DSpaceObject dSpaceObject) - throws SQLException; + public List findByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException; public void deleteByEPerson(Context context, EPerson eperson) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index 81428f16b52b..69dc53018928 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -46,27 +46,26 @@ public List findByEPerson(Context context, EPerson eperson) throws return list(context, criteriaQuery, false, Subscription.class, -1, -1); } - @Override - public Subscription findByCollectionAndEPerson(Context context, EPerson eperson, DSpaceObject dSpaceObject) - throws SQLException { + public List findByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); + criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson)); + criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson), - criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) - ) - ); - return singleResult(context, criteriaQuery); + criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) + )); + return list(context, criteriaQuery, false, Subscription.class, -1, -1); } @Override - public void deleteByCollection(Context context, Collection collection) throws SQLException { - String hqlQuery = "delete from Subscription where collection=:collection"; + public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException { + String hqlQuery = "delete from Subscription where dSpaceObject=:dSpaceObject"; Query query = createQuery(context, hqlQuery); - query.setParameter("collection", collection); + query.setParameter("dSpaceObject", dSpaceObject); query.executeUpdate(); } diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index c137fe2e7bc9..46c98eee1875 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -77,6 +77,16 @@ public void unsubscribe(Context context, EPerson eperson, */ public List getSubscriptionsByEPerson(Context context, EPerson eperson) throws SQLException; + /** + * Find out which collections an e-person is subscribed to and related with dso + * + * @param context DSpace context + * @param eperson EPerson + * @param dSpaceObject DSpaceObject + * @return array of collections e-person is subscribed to and related with dso + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public List getSubscriptionsByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException; /** * Find out which collections the currently logged in e-person can subscribe to * @@ -114,10 +124,10 @@ public boolean isSubscribed(Context context, EPerson eperson, * Delete subscription by collection. * * @param context DSpace context - * @param collection find out if subscribed to this collection + * @param dSpaceObject find out if subscribed to this dSpaceObject * @throws SQLException An exception that provides information on a database access error or other errors. */ - public void deleteByCollection(Context context, Collection collection) throws SQLException; + public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException; /** * Delete subscription by eperson (subscriber). diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index b37823c0fa04..e94ec3c5049f 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -12,6 +12,10 @@ CREATE SEQUENCE subscription_parameter_seq; +------------------------------------------------------- +-- Create the subscription_parameter table +------------------------------------------------------- + CREATE TABLE subscription_parameter ( subscription_parameter_id INTEGER NOT NULL, @@ -19,10 +23,10 @@ CREATE TABLE subscription_parameter value CHARACTER VARYING(255), subscription_id INTEGER NOT NULL, CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), - CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) + CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE ); -- -- -ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; +ALTER TABLE subscription DROP CONSTRAINT Subscription_collection_id_fk; -- ALTER TABLE subscription DROP COLUMN collection_id; -- @@ -30,7 +34,7 @@ ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; -- ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); -- -ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject(uuid); +ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index e668d9d7e569..8db385f5acf4 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -12,6 +12,9 @@ CREATE SEQUENCE subscription_parameter_seq; +----------------------------------------------------------------------------------- +-- ADD table subscription_parameter +----------------------------------------------------------------------------------- CREATE TABLE subscription_parameter ( subscription_parameter_id INTEGER NOT NULL, @@ -20,10 +23,10 @@ CREATE TABLE subscription_parameter subscription_id INTEGER NOT NULL, CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) - REFERENCES subscription (subscription_id) + REFERENCES subscription (subscription_id) ON DELETE CASCADE ); -- -- -ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey +ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; ---- -- ALTER TABLE subscription DROP COLUMN collection_id; -- diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index ee5cd1317d99..5313472706ee 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -12,6 +12,9 @@ CREATE SEQUENCE subscription_parameter_seq; +----------------------------------------------------------------------------------- +-- ADD table subscription_parameter +----------------------------------------------------------------------------------- CREATE TABLE subscription_parameter ( subscription_parameter_id INTEGER NOT NULL, @@ -19,7 +22,7 @@ CREATE TABLE subscription_parameter value CHARACTER VARYING(255), subscription_id INTEGER NOT NULL, CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), - CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) + CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE ); -- ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 2f3dbd72523f..d79619e579f0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -89,7 +89,7 @@ public Page findAllByEPerson(Context context, String id) { try { Context context = obtainContext(); EPerson ePerson = personService.findByIdOrLegacyId(context, id); - if (context.getCurrentUser().equals(ePerson) || authorizeService.isAdmin(context, ePerson)) { + if (context.getCurrentUser().equals(ePerson) || authorizeService.isAdmin(context, context.getCurrentUser())) { List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, ePerson); return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); } else { @@ -99,7 +99,33 @@ public Page findAllByEPerson(Context context, String id) { throw new RuntimeException(sqlException.getMessage(), sqlException); } } + @PreAuthorize("isAuthenticated()") + @SearchRestMethod(name = "findByEPersonAndDso") + public Page findByEPersonAndDso(Pageable pageable) throws Exception { + try { + Context context = obtainContext(); + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + String epersonId = req.getParameter("eperson_id"); + String dsoId = req.getParameter("dspace_object_id"); + DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); + EPerson ePerson = personService.findByIdOrLegacyId(context, epersonId); + // dso must always be set + if (dsoId == null || epersonId == null) { + throw new UnprocessableEntityException("error parsing the body"); + } + if (context.getCurrentUser().equals(ePerson) || authorizeService.isAdmin(context, context.getCurrentUser())) { + List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, ePerson); + return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); + } else { + throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); + } + } catch (SQLException sqlException) { + throw new SQLException(sqlException.getMessage(), sqlException); + } catch (AuthorizeException authorizeException) { + throw new AuthorizeException(authorizeException.getMessage()); + } + } @Override @PreAuthorize("isAuthenticated()") protected SubscriptionRest createAndReturn(Context context) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index 0a2f606d0ba0..9bb6214ed018 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.model.SubscriptionParameterRest; +import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.model.patch.AddOperation; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.RemoveOperation; @@ -29,6 +32,8 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import java.util.ArrayList; import java.util.HashMap; @@ -54,6 +59,7 @@ public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationT @Autowired SubscribeService subscribeService; + @Override @Before public void setUp() throws Exception { @@ -105,8 +111,20 @@ public void findAll() throws Exception { //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.page.size", is(20))); + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) + .andExpect(jsonPath("$._links.self.href", Matchers.is(REST_SERVER_URL + "core/subscriptions"))); } + @Test public void findByIdAsAdministrator() throws Exception { //When we call the root endpoint as anonymous user @@ -144,26 +162,21 @@ public void findByIdAsAdministrator() throws Exception { //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.type", is("TestType"))) - .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$.subscriptionType", is("TestType"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); } + @Test public void findByIdAsRandomUser() throws Exception { - //When we call the root endpoint as anonymous user - getClient().perform(get("/api/core/subscriptions")) - //The status has to be 403 Not Authorized - .andExpect(status().isUnauthorized()); - - String token = getAuthToken(eperson.getEmail(), password); + String token = getAuthToken(admin.getEmail(), password); context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -188,15 +201,16 @@ public void findByIdAsRandomUser() throws Exception { context.restoreAuthSystemState(); //When we call the root endpoint getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) - //The status has to be 200 OK - .andExpect(status().isUnauthorized()); + //The status has to be 403 + .andExpect(status().isForbidden()); } + @Test public void findAllSubscriptionsByEPerson() throws Exception { //When we call the root endpoint as anonymous user - getClient().perform(get("/api/core/subscriptions")) - //The status has to be 403 Not Authorized - .andExpect(status().isUnauthorized()); +// getClient().perform(get("/api/core/subscriptions")) +// //The status has to be 403 Not Authorized +// .andExpect(status().isUnauthorized()); String token = getAuthToken(eperson.getEmail(), password); context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) @@ -221,21 +235,23 @@ public void findAllSubscriptionsByEPerson() throws Exception { Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "TestType"); context.restoreAuthSystemState(); //When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/findByEPerson?id=" + eperson.getID())) + getClient(token).perform(get("/api/core/subscriptions/search/findByEPerson?id=" + eperson.getID())) //The status has to be 200 OK .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.type", is("TestType"))) - .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter1"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter1"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Parameter1"))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("ValueParameter1"))) + .andExpect(jsonPath("$._links.self.href", Matchers.is(REST_SERVER_URL + "core/subscriptions"))); + + EPerson epersonIT = EPersonBuilder.createEPerson(context) .withEmail("epersonIT@example.com") .withPassword(password) @@ -246,6 +262,7 @@ public void findAllSubscriptionsByEPerson() throws Exception { //The status has to be 200 OK .andExpect(status().isUnauthorized()); } + @Test public void addSubscriptionNotLoggedIn() throws Exception { context.turnOffAuthorisationSystem(); @@ -263,20 +280,27 @@ public void addSubscriptionNotLoggedIn() throws Exception { .withAuthor("John, Doe") .withSubject("Test") .build(); - List subscriptionParameterList = new ArrayList<>(); - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter1"); - subscriptionParameter.setValue("ValueParameter1"); - subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "TestType"); context.restoreAuthSystemState(); - //When we call the root endpoint as anonymous user - getClient().perform(post("/api/core/subscriptions")) - //The status has to be 403 Not Authorized + SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); + subscriptionParameterRest.setValue("nameTest"); + subscriptionParameterRest.setName("valueTest"); + List subscriptionParameterRestList = new ArrayList<>(); + subscriptionParameterRestList.add(subscriptionParameterRest); + SubscriptionRest subscriptionRest = new SubscriptionRest(); + subscriptionRest.setType("testType"); + MultiValueMap params = new LinkedMultiValueMap(); + params.add("dspace_object_id", publicItem1.getID().toString()); + params.add("eperson_id", eperson.getID().toString()); + ObjectMapper objectMapper = new ObjectMapper(); + getClient().perform(post("/api/core/subscriptions?dspace_object_id="+publicItem1.getID()+"&eperson_id="+eperson.getID()) + .content(objectMapper.writeValueAsString(subscriptionRest)) + .contentType(contentType)) + //The status has to be 401 Not Authorized .andExpect(status().isUnauthorized()); } + @Test - public void addSubscriptionAsLoggedIn() throws Exception { + public void addSubscriptionAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -292,30 +316,36 @@ public void addSubscriptionAsLoggedIn() throws Exception { .withAuthor("John, Doe") .withSubject("Test") .build(); - List subscriptionParameterList = new ArrayList<>(); - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Test"); - subscriptionParameter.setValue("Test"); - subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Type"); context.restoreAuthSystemState(); //When we call the root endpoint as anonymous user - getClient().perform(post("/api/core/subscriptions")) + SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); + subscriptionParameterRest.setValue("nameTest"); + subscriptionParameterRest.setName("valueTest"); + List subscriptionParameterRestList = new ArrayList<>(); + subscriptionParameterRestList.add(subscriptionParameterRest); + SubscriptionRest subscriptionRest = new SubscriptionRest(); + subscriptionRest.setType("testType"); +// subscriptionRest.setSubscriptionParameterList(subscriptionParameterRestList); + ObjectMapper objectMapper = new ObjectMapper(); + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/core/subscriptions?dspace_object_id="+publicItem1.getID()+"&eperson_id="+admin.getID()) + .content(objectMapper.writeValueAsString(subscriptionRest)) + .contentType(contentType)) //The status has to be 200 OK .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.type", is("Type"))) - .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Test"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("Test"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + .andExpect(jsonPath("$.subscriptionType", is("testType"))) + .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "core/dSpaceObject"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("nameTest"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("valueTest"))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("ePerson"))); } + @Test public void editSubscriptionAnonymous() throws Exception { context.turnOffAuthorisationSystem(); @@ -345,6 +375,7 @@ public void editSubscriptionAnonymous() throws Exception { //The status has to be 403 Not Authorized .andExpect(status().isUnauthorized()); } + @Test public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); @@ -380,6 +411,7 @@ public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { //The status has to be 403 Not Authorized .andExpect(status().isUnauthorized()); } + @Test public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -426,6 +458,7 @@ public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); } + @Test public void deleteSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); @@ -460,6 +493,7 @@ public void deleteSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { //The status has to be 403 Not Authorized .andExpect(status().isUnauthorized()); } + @Test public void deleteSubscriptionAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); @@ -489,6 +523,7 @@ public void deleteSubscriptionAsAdmin() throws Exception { //The status has to be 403 Not Authorized .andExpect(status().isOk()); } + @Test public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { parentCommunity = CommunityBuilder.createCommunity(context) @@ -516,10 +551,10 @@ public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { Map value = new HashMap<>(); value.put("name", "frequency"); value.put("value", "monthly"); - ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/"+subscription.getSubscriptionParameterList().get(0).getId(), value); + ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId(), value); ops.add(replaceOperation); String patchBody = getPatchContent(ops); - getClient(token).perform(patch("/api/core/subscriptions/"+subscription.getID()) + getClient(token).perform(patch("/api/core/subscriptions/" + subscription.getID()) .content(patchBody) ) //The status has to be 403 Not Authorized @@ -537,6 +572,7 @@ public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); } + @Test public void patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Exception { parentCommunity = CommunityBuilder.createCommunity(context) @@ -563,7 +599,7 @@ public void patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Excepti Map value = new HashMap<>(); value.put("name", "frequency"); value.put("value", "monthly"); - ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/"+subscription.getSubscriptionParameterList().get(0).getId(), value); + ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId(), value); ops.add(replaceOperation); String patchBody = getPatchContent(ops); EPerson epersonIT = EPersonBuilder.createEPerson(context) @@ -572,12 +608,13 @@ public void patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Excepti .withLanguage("al") .build(); String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); - getClient(epersonITtoken).perform(patch("/api/core/subscriptions/"+subscription.getID()) + getClient(epersonITtoken).perform(patch("/api/core/subscriptions/" + subscription.getID()) .content(patchBody) ) //The status has to be 200 OK .andExpect(status().isUnauthorized()); } + @Test public void patchAddSubscriptionParameter() throws Exception { parentCommunity = CommunityBuilder.createCommunity(context) @@ -605,10 +642,10 @@ public void patchAddSubscriptionParameter() throws Exception { Map value = new HashMap<>(); value.put("name", "frequency"); value.put("value", "monthly"); - AddOperation addOperation = new AddOperation("/subscriptionsParameter/"+subscription.getSubscriptionParameterList().get(0).getId(), value); + AddOperation addOperation = new AddOperation("/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId(), value); ops.add(addOperation); String patchBody = getPatchContent(ops); - getClient(token).perform(patch("/api/core/subscriptions/"+subscription.getID()) + getClient(token).perform(patch("/api/core/subscriptions/" + subscription.getID()) .content(patchBody) ) //The status has to be 403 Not Authorized @@ -628,6 +665,7 @@ public void patchAddSubscriptionParameter() throws Exception { .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); } + @Test public void patchRemoveSubscriptionParameter() throws Exception { context.turnOffAuthorisationSystem(); @@ -656,11 +694,11 @@ public void patchRemoveSubscriptionParameter() throws Exception { Map value = new HashMap<>(); value.put("name", "frequency"); value.put("value", "monthly"); - RemoveOperation removeOperation = new RemoveOperation("/subscriptionsParameter/"+subscription.getSubscriptionParameterList().get(0).getId()); + RemoveOperation removeOperation = new RemoveOperation("/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId()); ops.add(removeOperation); String patchBody = getPatchContent(ops); context.restoreAuthSystemState(); - getClient(token).perform(patch("/api/core/subscriptions/"+subscription.getID()) + getClient(token).perform(patch("/api/core/subscriptions/" + subscription.getID()) .content(patchBody) ) //The status has to be 403 Not Authorized From 141cde9899c12b5ca9bfe4127485090e00dd2d55 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 1 Dec 2022 18:51:24 +0100 Subject: [PATCH 078/394] [DSC-183] Fix checkstyle and refactor --- .../SubscribeParameterServiceImpl.java | 22 +- .../dspace/eperson/SubscribeServiceImpl.java | 45 +- .../java/org/dspace/eperson/Subscription.java | 7 +- .../dspace/eperson/SubscriptionParameter.java | 6 +- .../dspace/eperson/dao/SubscriptionDAO.java | 7 +- .../eperson/dao/SubscriptionParameterDAO.java | 2 +- .../eperson/dao/impl/SubscriptionDAOImpl.java | 16 +- .../impl/SubscriptionParameterDAOImpl.java | 3 +- .../eperson/service/SubscribeService.java | 61 +-- .../service/SubscriptionParameterService.java | 10 +- .../rest/converter/SubscriptionConverter.java | 11 +- .../app/rest/model/SubscriptionRest.java | 22 +- ...ubscriptionDSpaceObjectLinkRepository.java | 11 +- .../SubscriptionEPersonLinkRepository.java | 5 +- .../SubscriptionRestRepository.java | 51 ++- .../SubscriptionParameterAddOperation.java | 9 +- .../SubscriptionParameterRemoveOperation.java | 5 +- ...SubscriptionParameterReplaceOperation.java | 13 +- .../rest/SubscriptionRestRepositoryIT.java | 391 ++++++------------ 19 files changed, 293 insertions(+), 404 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java index 2bb2133dceba..88842ebfa95b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java @@ -2,11 +2,14 @@ * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at - *

+ * * http://www.dspace.org/license/ */ package org.dspace.eperson; +import java.sql.SQLException; +import java.util.List; + import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; @@ -14,8 +17,6 @@ import org.dspace.eperson.service.SubscriptionParameterService; import org.springframework.beans.factory.annotation.Autowired; -import java.sql.SQLException; -import java.util.List; /** * Class implemennting method for service layer of SubscriptionParameter entity @@ -42,16 +43,20 @@ public List findAll(Context context) throws SQLException } @Override - public SubscriptionParameter add(Context context, String name, String value, Subscription subscription) throws SQLException, AuthorizeException { - SubscriptionParameter subscriptionParameter = subscriptionParameterDAO.create(context, new SubscriptionParameter()); + public SubscriptionParameter add(Context context, String name, String value, + Subscription subscription) throws SQLException, AuthorizeException { + SubscriptionParameter subscriptionParameter = + subscriptionParameterDAO.create(context, new SubscriptionParameter()); subscriptionParameter.setName(name); subscriptionParameter.setSubscription(subscription); subscriptionParameter.setValue(value); return subscriptionParameter; } @Override - public SubscriptionParameter edit(Context context,Integer id,String value, String name, Subscription subscription) throws SQLException, AuthorizeException { - SubscriptionParameter subscriptionParameter = subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); + public SubscriptionParameter edit(Context context,Integer id,String value, + String name, Subscription subscription) throws SQLException, AuthorizeException { + SubscriptionParameter subscriptionParameter = + subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); subscriptionParameter.setId(id); subscriptionParameter.setName(name); subscriptionParameter.setSubscription(subscription); @@ -67,7 +72,8 @@ public SubscriptionParameter findById(Context context, int id) throws SQLExcepti @Override public void deleteSubscriptionParameter(Context context, Integer id) throws SQLException, AuthorizeException { - SubscriptionParameter subscriptionParameter = subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); + SubscriptionParameter subscriptionParameter = + subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); if (subscriptionParameter != null) { subscriptionParameter.setSubscription(null); subscriptionParameterDAO.delete(context, subscriptionParameter); diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 5f9ffdb6c550..b70342db00b4 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -2,7 +2,6 @@ * 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.eperson; @@ -61,8 +60,8 @@ public Subscription subscribe(Context context, EPerson eperson, || ((context.getCurrentUser() != null) && (context .getCurrentUser().getID().equals(eperson.getID())))) { Subscription new_subscription = subscriptionDAO.create(context, new Subscription()); - subscriptionParameterList.forEach(subscriptionParameter -> new_subscription.addParameter(subscriptionParameter)); -// new_subscription.setSubscriptionParameterList(subscriptionParameterList); + subscriptionParameterList.forEach(subscriptionParameter -> + new_subscription.addParameter(subscriptionParameter)); new_subscription.setePerson(eperson); new_subscription.setdSpaceObject(dSpaceObject); new_subscription.setType(type); @@ -75,20 +74,20 @@ public Subscription subscribe(Context context, EPerson eperson, @Override public void unsubscribe(Context context, EPerson eperson, - Collection collection) throws SQLException, AuthorizeException { + DSpaceObject dSpaceObject) throws SQLException, AuthorizeException { // Check authorisation. Must be administrator, or the eperson. if (authorizeService.isAdmin(context) || ((context.getCurrentUser() != null) && (context .getCurrentUser().getID().equals(eperson.getID())))) { - if (collection == null) { + if (dSpaceObject == null) { // Unsubscribe from all subscriptionDAO.deleteByEPerson(context, eperson); } else { - subscriptionDAO.deleteByCollectionAndEPerson(context, collection, eperson); + subscriptionDAO.deleteByDSOAndEPerson(context, dSpaceObject, eperson); - log.info(LogHelper.getHeader(context, "unsubscribe", - "eperson_id=" + eperson.getID() + ",collection_id=" - + collection.getID())); + log.info(LogManager.getHeader(context, "unsubscribe", + "eperson_id=" + eperson.getID() + ",collection_id=" + + dSpaceObject.getID())); } } else { throw new AuthorizeException( @@ -103,7 +102,8 @@ public List getSubscriptionsByEPerson(Context context, EPerson epe } @Override - public List getSubscriptionsByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject) + public List getSubscriptionsByEPersonAndDso(Context context, + EPerson eperson, DSpaceObject dSpaceObject) throws SQLException { return subscriptionDAO.findByEPerson(context, eperson); } @@ -159,7 +159,8 @@ public Subscription updateSubscription(Context context, Integer id, subscriptionDB.removeParameterList(); subscriptionDB.setType(type); subscriptionDB.setdSpaceObject(dSpaceObject); - subscriptionParameterList.forEach(subscriptionParameter -> subscriptionDB.addParameter(subscriptionParameter)); + subscriptionParameterList.forEach(subscriptionParameter -> + subscriptionDB.addParameter(subscriptionParameter)); subscriptionDB.setePerson(eperson); subscriptionDAO.save(context, subscriptionDB); return subscriptionDB; @@ -169,10 +170,12 @@ public Subscription updateSubscription(Context context, Integer id, } @Override - public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { + public Subscription addSubscriptionParameter(Context context, Integer id, + SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { // must be admin or the subscriber of the subscription Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); - if (authorizeService.isAdmin(context, context.getCurrentUser()) || subscriptionDB.getePerson().equals(context.getCurrentUser())) { + if (authorizeService.isAdmin(context, context.getCurrentUser()) + || subscriptionDB.getePerson().equals(context.getCurrentUser())) { subscriptionDB.addParameter(subscriptionParameter); subscriptionDAO.save(context, subscriptionDB); return subscriptionDB; @@ -182,10 +185,12 @@ public Subscription addSubscriptionParameter(Context context, Integer id, Subscr } @Override - public Subscription removeSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { + public Subscription removeSubscriptionParameter(Context context, Integer id, + SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { // must be admin or the subscriber of the subscription Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); - if (authorizeService.isAdmin(context, context.getCurrentUser()) || subscriptionDB.getePerson().equals(context.getCurrentUser())) { + if (authorizeService.isAdmin(context, context.getCurrentUser()) + || subscriptionDB.getePerson().equals(context.getCurrentUser())) { subscriptionDB.removeParameter(subscriptionParameter); subscriptionDAO.save(context, subscriptionDB); return subscriptionDB; @@ -200,8 +205,14 @@ public void deleteSubscription(Context context, Integer id) throws SQLException, Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); if (subscription != null) { // must be admin or the subscriber of the subscription - if (authorizeService.isAdmin(context, context.getCurrentUser()) || subscription.getePerson().equals(context.getCurrentUser())) { - subscriptionDAO.delete(context, subscription); + if (authorizeService.isAdmin(context, context.getCurrentUser()) + || subscription.getePerson().equals(context.getCurrentUser())) { + try { + subscriptionDAO.delete(context, subscription); + } catch (SQLException sqlException) { + throw new SQLException(sqlException); + } + } else { throw new AuthorizeException("Only admin or e-person themselves can delete the subscription"); } diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index 0824e9fdd66f..48dd24cdd452 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -7,6 +7,8 @@ */ package org.dspace.eperson; +import java.util.ArrayList; +import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -17,17 +19,14 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; -import javax.persistence.OrderBy; import javax.persistence.SequenceGenerator; import javax.persistence.Table; -import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; -import java.util.ArrayList; -import java.util.List; + /** * Database entity representation of the subscription table diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java index c34dcb9c5dfb..0b69b365155b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java @@ -9,7 +9,6 @@ import javax.persistence.Column; -import javax.persistence.DiscriminatorColumn; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; @@ -31,8 +30,9 @@ public class SubscriptionParameter { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "subscription_parameter_seq") - @SequenceGenerator(name = "subscription_parameter_seq", sequenceName = "subscription_parameter_seq", allocationSize = 1) - @Column(name = "subscription_parameter_id", unique = true, nullable = false, insertable = true) + @SequenceGenerator(name = "subscription_parameter_seq", + sequenceName = "subscription_parameter_seq", allocationSize = 1) + @Column(name = "subscription_parameter_id", unique = true) private Integer id; @ManyToOne @JoinColumn(name = "subscription_id") diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index 81b89aab894b..5c69e76e963d 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -2,7 +2,6 @@ * 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.eperson.dao; @@ -10,7 +9,6 @@ import java.sql.SQLException; import java.util.List; -import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.core.GenericDAO; @@ -31,11 +29,12 @@ public interface SubscriptionDAO extends GenericDAO { public List findByEPerson(Context context, EPerson eperson) throws SQLException; - public List findByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException; + public List findByEPersonAndDso(Context context, + EPerson eperson, DSpaceObject dSpaceObject) throws SQLException; public void deleteByEPerson(Context context, EPerson eperson) throws SQLException; - public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson) + public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) throws SQLException; public List findAllOrderedByEPerson(Context context) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java index 3a8556472d45..ea9c7b0bbd37 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionParameterDAO.java @@ -6,10 +6,10 @@ * http://www.dspace.org/license/ */ package org.dspace.eperson.dao; - import org.dspace.core.GenericDAO; import org.dspace.eperson.SubscriptionParameter; + /** * Database Access Object interface class for the SubscriptionParamter object. * The implementation of this class is responsible for all database calls for the SubscriptionParameter object and is diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index 69dc53018928..e201d160cd4e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -15,7 +15,6 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; -import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; @@ -47,14 +46,17 @@ public List findByEPerson(Context context, EPerson eperson) throws } @Override - public List findByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException { + public List findByEPersonAndDso(Context context, EPerson eperson, + DSpaceObject dSpaceObject) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); + javax.persistence.criteria.CriteriaQuery criteriaQuery = + getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson)); - criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson), + criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal( + subscriptionRoot.get(Subscription_.ePerson), eperson), criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) )); return list(context, criteriaQuery, false, Subscription.class, -1, -1); @@ -78,11 +80,11 @@ public void deleteByEPerson(Context context, EPerson eperson) throws SQLExceptio } @Override - public void deleteByCollectionAndEPerson(Context context, Collection collection, EPerson eperson) + public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) throws SQLException { - String hqlQuery = "delete from Subscription where collection=:collection AND ePerson=:ePerson"; + String hqlQuery = "delete from Subscription where dSpaceObject=:dSpaceObject AND ePerson=:ePerson"; Query query = createQuery(context, hqlQuery); - query.setParameter("collection", collection); + query.setParameter("dSpaceObject", dSpaceObject); query.setParameter("ePerson", eperson); query.executeUpdate(); } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java index a13696cc5aa6..eee80a73a510 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java @@ -18,7 +18,8 @@ * * @author Alba Aliu at atis.al */ -public class SubscriptionParameterDAOImpl extends AbstractHibernateDAO implements SubscriptionParameterDAO { +public class SubscriptionParameterDAOImpl extends AbstractHibernateDAO + implements SubscriptionParameterDAO { protected SubscriptionParameterDAOImpl() { super(); } diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 46c98eee1875..9c763af4c96d 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -2,7 +2,7 @@ * 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.eperson.service; @@ -42,7 +42,7 @@ public interface SubscribeService { * Subscribe an e-person to a collection. An e-mail will be sent every day a * new item appears in the collection. * - * @param context DSpace context + * @param context DSpace context * @throws SQLException An exception that provides information on a database access error or other errors. * @throws AuthorizeException Exception indicating the current user of the context does not have permission * to perform a particular action. @@ -50,22 +50,22 @@ public interface SubscribeService { public Subscription subscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject, List subscriptionParameterList, - String type ) throws SQLException, AuthorizeException; + String type) throws SQLException, AuthorizeException; /** * Unsubscribe an e-person to a collection. Passing in null * for the collection unsubscribes the e-person from all collections they * are subscribed to. * - * @param context DSpace context - * @param eperson EPerson to unsubscribe - * @param collection Collection to unsubscribe from + * @param context DSpace context + * @param eperson EPerson to unsubscribe + * @param dSpaceObject DSpaceObject to unsubscribe from * @throws SQLException An exception that provides information on a database access error or other errors. * @throws AuthorizeException Exception indicating the current user of the context does not have permission * to perform a particular action. */ public void unsubscribe(Context context, EPerson eperson, - Collection collection) throws SQLException, AuthorizeException; + DSpaceObject dSpaceObject) throws SQLException, AuthorizeException; /** * Find out which collections an e-person is subscribed to @@ -80,13 +80,16 @@ public void unsubscribe(Context context, EPerson eperson, /** * Find out which collections an e-person is subscribed to and related with dso * - * @param context DSpace context - * @param eperson EPerson + * @param context DSpace context + * @param eperson EPerson * @param dSpaceObject DSpaceObject * @return array of collections e-person is subscribed to and related with dso * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getSubscriptionsByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException; + public List getSubscriptionsByEPersonAndDso(Context context, + EPerson eperson, + DSpaceObject dSpaceObject) throws SQLException; + /** * Find out which collections the currently logged in e-person can subscribe to * @@ -111,8 +114,8 @@ public List getAvailableSubscriptions(Context context, EPerson epers /** * Is that e-person subscribed to that collection? * - * @param context DSpace context - * @param eperson find out if this e-person is subscribed + * @param context DSpace context + * @param eperson find out if this e-person is subscribed * @param dSpaceObject find out if subscribed to this dSpaceObject * @return true if they are subscribed * @throws SQLException An exception that provides information on a database access error or other errors. @@ -123,7 +126,7 @@ public boolean isSubscribed(Context context, EPerson eperson, /** * Delete subscription by collection. * - * @param context DSpace context + * @param context DSpace context * @param dSpaceObject find out if subscribed to this dSpaceObject * @throws SQLException An exception that provides information on a database access error or other errors. */ @@ -150,37 +153,41 @@ public boolean isSubscribed(Context context, EPerson eperson, /** * Updates a subscription by id * - * @param context DSpace context - * @param id Integer id - * @param eperson EPerson eperson - * @param dSpaceObject DSpaceObject dSpaceObject - * @param subscriptionParameterList List subscriptionParameterList - * @param type String type + * @param context DSpace context + * @param id Integer id + * @param eperson EPerson eperson + * @param dSpaceObject DSpaceObject dSpaceObject + * @param subscriptionParameterList List subscriptionParameterList + * @param type String type * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription updateSubscription(Context context, Integer id, EPerson eperson, + public Subscription updateSubscription(Context context, Integer id, EPerson eperson, DSpaceObject dSpaceObject, List subscriptionParameterList, - String type) throws SQLException, AuthorizeException; + String type) throws SQLException, AuthorizeException; /** * Adds a parameter to a subscription * - * @param context DSpace context - * @param id Integer id + * @param context DSpace context + * @param id Integer id * @param subscriptionParameter SubscriptionParameter subscriptionParameter * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; + public Subscription addSubscriptionParameter(Context context, Integer id, + SubscriptionParameter subscriptionParameter) + throws SQLException, AuthorizeException; + /** * Deletes a parameter from subscription * - * @param context DSpace context - * @param id Integer id + * @param context DSpace context + * @param id Integer id * @param subscriptionParameter SubscriptionParameter subscriptionParameter * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription removeSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; + public Subscription removeSubscriptionParameter(Context context, Integer id, + SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; /** * Deletes a subscription diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java index 1870457437dc..eda5d6c570f8 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java @@ -7,18 +7,18 @@ */ package org.dspace.eperson.service; +import java.sql.SQLException; +import java.util.List; + import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; -import java.sql.SQLException; -import java.util.List; - /** * Service interface class for the SubscriptionParameter object. - * The implementation of this class is responsible for all business logic calls for the SubscriptionParameter object and is - * autowired by spring + * The implementation of this class is responsible for all business logic calls for + * the SubscriptionParameter object and is autowired by spring * Class defining methods for sending new item e-mail alerts to users * * @author Alba Aliu @atis.al diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java index 5198c622bbbc..7e7ce6ee495b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java @@ -7,6 +7,10 @@ */ package org.dspace.app.rest.converter; +import java.util.ArrayList; +import java.util.List; + +import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.Utils; @@ -16,7 +20,7 @@ /** * This is the converter from Entity Subscription to the REST data model - * + * * @author Alba Aliu at atis.al * */ @@ -24,12 +28,9 @@ public class SubscriptionConverter implements DSpaceConverter { @Autowired protected Utils utils; - /* (non-Javadoc) - * @see org.dspace.app.rest.converter.DSpaceConverter#convert - * (java.lang.Object, org.dspace.app.rest.projection.Projection) - */ @Autowired private ConverterService converter; + @Override public SubscriptionRest convert(Subscription subscription, Projection projection) { SubscriptionRest rest = new SubscriptionRest(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index ff36755065aa..d158d3ba91a8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -2,28 +2,23 @@ * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at - *

+ * * http://www.dspace.org/license/ */ package org.dspace.app.rest.model; -import org.dspace.app.rest.RestResourceController; - import java.util.ArrayList; import java.util.List; -@LinksRest(links = { - @LinkRest( - name = SubscriptionRest.DSPACE_OBJECT, - method = "getDSpaceObject" - ), - @LinkRest( - name = SubscriptionRest.EPERSON, - method = "getEPerson" - ) +import org.dspace.app.rest.RestResourceController; + +@LinksRest(links = {@LinkRest(name = SubscriptionRest.DSPACE_OBJECT, + method = "getDSpaceObject"), @LinkRest( + name = SubscriptionRest.EPERSON, + method = "getEPerson") }) -public class SubscriptionRest extends BaseObjectRest{ +public class SubscriptionRest extends BaseObjectRest { public static final String NAME = "subscription"; public static final String NAME_PLURAL = "subscriptions"; public static final String CATEGORY = "core"; @@ -60,6 +55,7 @@ public List getSubscriptionParameterList() { public void setSubscriptionParameterList(List subscriptionParameterList) { this.subscriptionParameterList = subscriptionParameterList; } + public String getSubscriptionType() { return this.type; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index 77279495f035..b36a7509977b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -2,15 +2,18 @@ * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at - *

+ * * http://www.dspace.org/license/ */ package org.dspace.app.rest.repository; +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; -import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.SubscribeService; @@ -21,10 +24,6 @@ import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.stereotype.Component; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import java.sql.SQLException; - /** * Link repository for "dataSpaceObject" of subscription */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index dfe35e36e268..2e24db95967e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -7,9 +7,10 @@ */ package org.dspace.app.rest.repository; +import java.sql.SQLException; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; -import java.sql.SQLException; + import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; @@ -21,8 +22,6 @@ import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.stereotype.Component; - - /** * Link repository for "eperson" of subscription */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index d79619e579f0..271ea533992a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -2,11 +2,20 @@ * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at - *

+ * * http://www.dspace.org/license/ */ package org.dspace.app.rest.repository; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.LogManager; @@ -15,6 +24,9 @@ import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.SubscriptionRest; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.patch.ResourcePatch; +import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; @@ -43,7 +55,8 @@ */ @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME) -public class SubscriptionRestRepository extends DSpaceRestRepository { +public class SubscriptionRestRepository extends DSpaceRestRepository + implements LinkRestRepository { private static final Logger log = LogManager.getLogger(); @Autowired AuthorizeService authorizeService; @@ -60,7 +73,7 @@ public class SubscriptionRestRepository extends DSpaceRestRepository findAllByEPerson(Context context, String id) { try { Context context = obtainContext(); EPerson ePerson = personService.findByIdOrLegacyId(context, id); - if (context.getCurrentUser().equals(ePerson) || authorizeService.isAdmin(context, context.getCurrentUser())) { + if (context.getCurrentUser().equals(ePerson) + || authorizeService.isAdmin(context, context.getCurrentUser())) { List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, ePerson); return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); } else { @@ -113,9 +127,12 @@ public Page findByEPersonAndDso(Pageable pageable) throws Exce if (dsoId == null || epersonId == null) { throw new UnprocessableEntityException("error parsing the body"); } - if (context.getCurrentUser().equals(ePerson) || authorizeService.isAdmin(context, context.getCurrentUser())) { - List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, ePerson); - return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); + if (context.getCurrentUser().equals(ePerson) + || authorizeService.isAdmin(context, context.getCurrentUser())) { + List subscriptionList = + subscribeService.getSubscriptionsByEPersonAndDso(context, ePerson, dSpaceObject); + return converter.toRestPage(subscriptionList, pageable, subscriptionList.size(), + utils.obtainProjection()); } else { throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); } @@ -184,9 +201,18 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri } catch (SQLException e) { throw new ResourceNotFoundException(notFoundException); } - if (id.equals(subscriptionRest.getId())) { - subscription = subscribeService.updateSubscription(context, subscriptionRest.getId(), null, - null, subscriptionRest.getSubscriptionParameterList(), subscriptionRest.getType()); + if (id.equals(subscription.getID())) { + List subscriptionParameterList = new ArrayList<>(); + for (SubscriptionParameterRest subscriptionParameterRest : + subscriptionRest.getSubscriptionParameterList()) { + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setSubscription(subscription); + subscriptionParameter.setValue(subscriptionParameterRest.getValue()); + subscriptionParameter.setName(subscriptionParameterRest.getName()); + subscriptionParameterList.add(subscriptionParameter); + } + subscription = subscribeService.updateSubscription(context, id, ePerson, + dSpaceObject, subscriptionParameterList, subscriptionRest.getSubscriptionType()); return converter.toRest(subscription, utils.obtainProjection()); } else { throw new IllegalArgumentException("The id in the Json and the id in the url do not match: " @@ -197,8 +223,9 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri @Override @PreAuthorize("isAuthenticated()") - public void patch(Context context, HttpServletRequest request, String apiCategory, String model, Integer id, Patch patch) - throws UnprocessableEntityException, DSpaceBadRequestException { + public void patch(Context context, HttpServletRequest request, String apiCategory, + String model, Integer id, Patch patch) + throws UnprocessableEntityException, DSpaceBadRequestException, AuthorizeException { Subscription subscription = null; try { subscription = subscribeService.findById(context, id); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java index 4ea712c48a1c..2393d49f2306 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java @@ -2,11 +2,13 @@ * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at - *

+ * * http://www.dspace.org/license/ */ package org.dspace.app.rest.repository.patch.operation; +import java.sql.SQLException; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -21,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.sql.SQLException; + /** * Implementation for SubscriptionParameterAddOperation patches. @@ -50,7 +52,8 @@ public Subscription perform(Context context, Subscription subscription, Operatio } else { value = objectMapper.readTree((String) operation.getValue()); } - SubscriptionParameterRest subscriptionParameterRest = objectMapper.readValue(value.toString(), SubscriptionParameterRest.class); + SubscriptionParameterRest subscriptionParameterRest = + objectMapper.readValue(value.toString(), SubscriptionParameterRest.class); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setSubscription(subscription); subscriptionParameter.setValue(subscriptionParameterRest.getValue()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java index de665c4cb2ec..660ed7f8d4f1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java @@ -2,11 +2,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.repository.patch.operation; +import java.sql.SQLException; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RESTAuthorizationException; @@ -20,8 +21,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.sql.SQLException; - /** * Implementation for SubscriptionParameterRemoveOperation patches. *

diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java index 570c35e772a4..b0f7c9a31c50 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java @@ -2,11 +2,13 @@ * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at - *

+ * * http://www.dspace.org/license/ */ package org.dspace.app.rest.repository.patch.operation; +import java.sql.SQLException; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.exception.DSpaceBadRequestException; @@ -22,9 +24,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.sql.SQLException; - - /** * Implementation for SubscriptionParameterReplaceOperation patches. *

@@ -54,9 +53,11 @@ public Subscription perform(Context context, Subscription subscription, Operatio } else { value = objectMapper.readTree((String) operation.getValue()); } - SubscriptionParameterRest subscriptionParameterRest = objectMapper.readValue(value.toString(), SubscriptionParameterRest.class); + SubscriptionParameterRest subscriptionParameterRest = objectMapper.readValue( + value.toString(), SubscriptionParameterRest.class); try { - SubscriptionParameter subscriptionParameter = subscriptionParameterService.edit(context, subscriptionParameterId, subscriptionParameterRest.getValue(), + SubscriptionParameter subscriptionParameter = subscriptionParameterService.edit(context, + subscriptionParameterId, subscriptionParameterRest.getValue(), subscriptionParameterRest.getName(), subscription); } catch (SQLException | AuthorizeException exception) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index 9bb6214ed018..eccfb647a827 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -2,7 +2,7 @@ * 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; @@ -27,10 +27,10 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; -import org.dspace.eperson.service.SubscribeService; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; +import org.springframework.http.MediaType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -41,7 +41,6 @@ import java.util.Map; import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; @@ -56,52 +55,45 @@ * (Class has to start or end with IT to be picked up by the failsafe plugin) */ public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationTest { - - @Autowired - SubscribeService subscribeService; + private Collection collection; + private Item publicItem; @Override @Before public void setUp() throws Exception { super.setUp(); - // We turn off the authorization system in order to create the structure as - // defined below -// context.turnOffAuthorisationSystem(); -// parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); -// colPeople = CollectionBuilder.createCollection(context, parentCommunity).withName("People") -// .withEntityType("Person").build(); -// context.restoreAuthSystemState(); - } - - @Test - public void findAll() throws Exception { context.turnOffAuthorisationSystem(); - //When we call the root endpoint as anonymous user - getClient().perform(get("/api/core/subscriptions")) - //The status has to be 401 Not Authorized - .andExpect(status().isUnauthorized()); - - String token = getAuthToken(admin.getEmail(), password); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + Community community = CommunityBuilder.createSubCommunity(context, parentCommunity) .withName("Sub Community") .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + collection = CollectionBuilder.createCollection(context, community).withName("Collection 1").build(); // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) + publicItem = ItemBuilder.createItem(context, collection) .withTitle("Test") .withIssueDate("2010-10-17") .withAuthor("Smith, Donald") .withSubject("ExtraEntry") .build(); + context.restoreAuthSystemState(); + } + + @Test + public void findAll() throws Exception { + context.turnOffAuthorisationSystem(); + //When we call the root endpoint as anonymous user + getClient().perform(get("/api/core/subscriptions")) + //The status has to be 401 Not Authorized + .andExpect(status().isUnauthorized()); + String token = getAuthToken(admin.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, admin, publicItem1, subscriptionParameterList, "TypeTest"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TypeTest", publicItem, admin, subscriptionParameterList).build(); subscriptionParameter.setSubscription(subscription); //When we call the root endpoint context.restoreAuthSystemState(); @@ -127,33 +119,18 @@ public void findAll() throws Exception { @Test public void findByIdAsAdministrator() throws Exception { + context.turnOffAuthorisationSystem(); //When we call the root endpoint as anonymous user getClient().perform(get("/api/core/subscriptions")) //The status has to be 403 Not Authorized .andExpect(status().isUnauthorized()); - String token = getAuthToken(admin.getEmail(), password); - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter"); subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, admin, publicItem1, subscriptionParameterList, "TestType"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); //When we call the root endpoint getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) @@ -175,34 +152,19 @@ public void findByIdAsAdministrator() throws Exception { } @Test - public void findByIdAsRandomUser() throws Exception { - String token = getAuthToken(admin.getEmail(), password); + public void findByIdAsAnonymous() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter"); subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, admin, publicItem1, subscriptionParameterList, "TestType"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); //When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) - //The status has to be 403 - .andExpect(status().isForbidden()); + getClient().perform(get("/api/core/subscriptions/" + subscription.getID())) + //The status has to be 401 + .andExpect(status().isUnauthorized()); } @Test @@ -213,26 +175,16 @@ public void findAllSubscriptionsByEPerson() throws Exception { // .andExpect(status().isUnauthorized()); String token = getAuthToken(eperson.getEmail(), password); context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") + EPerson user = EPersonBuilder.createEPerson(context) + .withEmail("user@test.it") + .withPassword(password) .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter1"); subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "TestType"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, user, subscriptionParameterList).build(); context.restoreAuthSystemState(); //When we call the root endpoint getClient(token).perform(get("/api/core/subscriptions/search/findByEPerson?id=" + eperson.getID())) @@ -242,14 +194,13 @@ public void findAllSubscriptionsByEPerson() throws Exception { .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TestType"))) .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Parameter1"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("ValueParameter1"))) - .andExpect(jsonPath("$._links.self.href", Matchers.is(REST_SERVER_URL + "core/subscriptions"))); + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("ValueParameter1"))); EPerson epersonIT = EPersonBuilder.createEPerson(context) @@ -257,30 +208,28 @@ public void findAllSubscriptionsByEPerson() throws Exception { .withPassword(password) .withLanguage("al") .build(); - String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); - getClient(epersonITtoken).perform(get("/api/core/subscriptions/" + subscription.getID())) + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter1"); + subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameterList.add(subscriptionParameter); + List subscriptionParameterList1 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter1 = new SubscriptionParameter(); + subscriptionParameter1.setName("Parameter1"); + subscriptionParameter1.setValue("ValueParameter1"); + subscriptionParameterList1.add(subscriptionParameter1); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", collection, user, subscriptionParameterList).build(); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, "Test", collection, user, subscriptionParameterList1).build(); + context.restoreAuthSystemState(); + //When we call the root endpoint + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/core/subscriptions/search/findByEPersonAndDso?dspace_object_id=" + collection.getID() + "&eperson_id=" + user.getID())) //The status has to be 200 OK .andExpect(status().isUnauthorized()); } @Test public void addSubscriptionNotLoggedIn() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); - context.restoreAuthSystemState(); SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); subscriptionParameterRest.setValue("nameTest"); subscriptionParameterRest.setName("valueTest"); @@ -289,11 +238,11 @@ public void addSubscriptionNotLoggedIn() throws Exception { SubscriptionRest subscriptionRest = new SubscriptionRest(); subscriptionRest.setType("testType"); MultiValueMap params = new LinkedMultiValueMap(); - params.add("dspace_object_id", publicItem1.getID().toString()); + params.add("dspace_object_id", publicItem.getID().toString()); params.add("eperson_id", eperson.getID().toString()); ObjectMapper objectMapper = new ObjectMapper(); - getClient().perform(post("/api/core/subscriptions?dspace_object_id="+publicItem1.getID()+"&eperson_id="+eperson.getID()) - .content(objectMapper.writeValueAsString(subscriptionRest)) + getClient().perform(post("/api/core/subscriptions?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + eperson.getID()) + .content(objectMapper.writeValueAsString(subscriptionRest)) .contentType(contentType)) //The status has to be 401 Not Authorized .andExpect(status().isUnauthorized()); @@ -301,22 +250,6 @@ public void addSubscriptionNotLoggedIn() throws Exception { @Test public void addSubscriptionAsAdmin() throws Exception { - context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); - context.restoreAuthSystemState(); //When we call the root endpoint as anonymous user SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); subscriptionParameterRest.setValue("nameTest"); @@ -328,9 +261,17 @@ public void addSubscriptionAsAdmin() throws Exception { // subscriptionRest.setSubscriptionParameterList(subscriptionParameterRestList); ObjectMapper objectMapper = new ObjectMapper(); String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(post("/api/core/subscriptions?dspace_object_id="+publicItem1.getID()+"&eperson_id="+admin.getID()) - .content(objectMapper.writeValueAsString(subscriptionRest)) - .contentType(contentType)) + Map map = new HashMap<>(); + map.put("type", "test"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "daily"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + getClient(token).perform(post("/api/core/subscriptions?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + admin.getID()) + .content(objectMapper.writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) //The status has to be 200 OK .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" @@ -349,29 +290,27 @@ public void addSubscriptionAsAdmin() throws Exception { @Test public void editSubscriptionAnonymous() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter1"); subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "TestType"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + ObjectMapper objectMapper = new ObjectMapper(); + String token = getAuthToken(admin.getEmail(), password); + Map newSubscription = new HashMap<>(); + newSubscription.put("type", "test"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "daily"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); context.restoreAuthSystemState(); //When we call the root endpoint as anonymous user - getClient().perform(put("/api/core/subscriptions")) + getClient().perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + admin.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) //The status has to be 403 Not Authorized .andExpect(status().isUnauthorized()); } @@ -384,30 +323,17 @@ public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { .withPassword(password) .withLanguage("al") .build(); - String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter1"); subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "TestType"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); //When we call the root endpoint as anonymous user - getClient(epersonITtoken).perform(put("/api/core/subscriptions")) + getClient().perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + admin.getID()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) //The status has to be 403 Not Authorized .andExpect(status().isUnauthorized()); } @@ -417,46 +343,39 @@ public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); String tokenSubscriber = getAuthToken(eperson.getEmail(), password); context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - getClient(tokenSubscriber).perform(put("/api/core/subscriptions")) - //The status has to be 403 Not Authorized - .andExpect(status().isOk()); - //When we call the root endpoint as anonymous user - getClient(tokenAdmin).perform(put("/api/core/subscriptions")) - //The status has to be 403 Not Authorized + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("type", "test"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "daily"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + eperson.getID()) + //The status has to be 403 Not Authorized + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.type", is("Test"))) - .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("Daily"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + .andExpect(jsonPath("$.subscriptionType", is("test"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) + .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); } @Test @@ -468,26 +387,12 @@ public void deleteSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { .withLanguage("al") .build(); String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient(epersonITtoken).perform(put("/api/core/subscriptions")) //The status has to be 403 Not Authorized @@ -497,26 +402,13 @@ public void deleteSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { @Test public void deleteSubscriptionAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); + String token = getAuthToken(admin.getEmail(), password); context.restoreAuthSystemState(); String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(put("/api/core/subscriptions")) @@ -526,26 +418,13 @@ public void deleteSubscriptionAsAdmin() throws Exception { @Test public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); + context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("TestName"); subscriptionParameter.setValue("TestValue"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); String token = getAuthToken(admin.getEmail(), password); List ops = new ArrayList(); Map value = new HashMap<>(); @@ -575,26 +454,13 @@ public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { @Test public void patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Exception { - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); + context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("TestName"); subscriptionParameter.setValue("TestValue"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); List ops = new ArrayList(); Map value = new HashMap<>(); value.put("name", "frequency"); @@ -611,32 +477,19 @@ public void patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Excepti getClient(epersonITtoken).perform(patch("/api/core/subscriptions/" + subscription.getID()) .content(patchBody) ) - //The status has to be 200 OK - .andExpect(status().isUnauthorized()); + //The status has to be 403 Forbidden + .andExpect(status().isForbidden()); } @Test public void patchAddSubscriptionParameter() throws Exception { - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); + context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("TestName"); subscriptionParameter.setValue("TestValue"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); String token = getAuthToken(admin.getEmail(), password); List ops = new ArrayList(); Map value = new HashMap<>(); @@ -669,26 +522,12 @@ public void patchAddSubscriptionParameter() throws Exception { @Test public void patchRemoveSubscriptionParameter() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - // creation of the item which will be the DSO related with a subscription - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Item") - .withIssueDate("2020-10-17") - .withAuthor("John, Doe") - .withSubject("Test") - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("TestName"); subscriptionParameter.setValue("TestValue"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = subscribeService.subscribe(context, eperson, publicItem1, subscriptionParameterList, "Test"); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); String token = getAuthToken(admin.getEmail(), password); List ops = new ArrayList(); Map value = new HashMap<>(); From 3ac40b9761082e219fc0b9a9369f344042151a44 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 1 Dec 2022 18:54:35 +0100 Subject: [PATCH 079/394] [DSC-183] new paramater of type of dso in findall and pagination usage in query --- .../org/dspace/eperson/SubscribeCLITool.java | 8 +++-- .../dspace/eperson/SubscribeServiceImpl.java | 26 +++++++++++----- .../java/org/dspace/eperson/Subscription.java | 1 - .../dspace/eperson/SubscriptionParameter.java | 1 - .../dspace/eperson/dao/SubscriptionDAO.java | 9 ++++-- .../eperson/dao/impl/SubscriptionDAOImpl.java | 31 +++++++++++++++---- .../eperson/service/SubscribeService.java | 19 +++++++++--- .../SubscriptionRestRepository.java | 15 ++++++--- 8 files changed, 80 insertions(+), 30 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java index b8f5d8be346f..b19ad78ff647 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java @@ -93,8 +93,12 @@ public static void processDaily(Context context, boolean test) throws SQLExcepti IOException { // Grab the subscriptions - List subscriptions = subscribeService.findAll(context); - + List subscriptions = new ArrayList<>(); + try { + subscriptions = subscribeService.findAll(context, null, -1, -1); + } catch (Exception e) { + log.error(e.getMessage()); + } EPerson currentEPerson = null; List collections = null; // List of Collections diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index b70342db00b4..f6f4a7953fa7 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -46,8 +46,18 @@ protected SubscribeServiceImpl() { } @Override - public List findAll(Context context) throws SQLException { - return subscriptionDAO.findAllOrderedByEPerson(context); + public List findAll(Context context, String resourceType, + Integer limit, Integer offset) throws Exception { + if (resourceType == null) { + return subscriptionDAO.findAllOrderedByEPerson(context); + } else { + if (resourceType.equals("Item") || resourceType.equals("Collection") || resourceType.equals("Community")) { + return subscriptionDAO.findAllOrderedByEPersonAndResourceType(context, resourceType, limit, offset); + } else { + log.error("Resource type must be Item, Collection or Community"); + throw new Exception("Resource type must be Item, Collection or Community"); + } + } } @Override @@ -96,16 +106,17 @@ public void unsubscribe(Context context, EPerson eperson, } @Override - public List getSubscriptionsByEPerson(Context context, EPerson eperson) + public List getSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) throws SQLException { - return subscriptionDAO.findByEPerson(context, eperson); + return subscriptionDAO.findByEPerson(context, eperson, limit, offset); } @Override public List getSubscriptionsByEPersonAndDso(Context context, - EPerson eperson, DSpaceObject dSpaceObject) + EPerson eperson, DSpaceObject dSpaceObject, + Integer limit, Integer offset) throws SQLException { - return subscriptionDAO.findByEPerson(context, eperson); + return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, limit, offset); } @Override @@ -122,14 +133,13 @@ public List getAvailableSubscriptions(Context context, EPerson epers context.setCurrentUser(eperson); } collections = collectionService.findAuthorized(context, null, Constants.ADD); - return collections; } @Override public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException { - return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject) != null; + return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, -1, -1) != null; } @Override diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index 48dd24cdd452..d7090ed7ddad 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -2,7 +2,6 @@ * 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.eperson; diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java index 0b69b365155b..04acbf7bbf71 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java @@ -2,7 +2,6 @@ * 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.eperson; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index 5c69e76e963d..92a06c98f67d 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -27,15 +27,20 @@ public interface SubscriptionDAO extends GenericDAO { public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException; - public List findByEPerson(Context context, EPerson eperson) throws SQLException; + public List findByEPerson(Context context, + EPerson eperson, Integer limit, Integer offset) throws SQLException; public List findByEPersonAndDso(Context context, - EPerson eperson, DSpaceObject dSpaceObject) throws SQLException; + EPerson eperson, DSpaceObject dSpaceObject, + Integer limit, Integer offset) throws SQLException; public void deleteByEPerson(Context context, EPerson eperson) throws SQLException; public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) throws SQLException; + public List findAllOrderedByEPersonAndResourceType(Context context, String resourceType, + Integer limit, Integer offset) throws SQLException; + public List findAllOrderedByEPerson(Context context) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index e201d160cd4e..7b7b292aeecc 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -2,7 +2,6 @@ * 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.eperson.dao.impl; @@ -36,18 +35,21 @@ protected SubscriptionDAOImpl() { } @Override - public List findByEPerson(Context context, EPerson eperson) throws SQLException { + public List findByEPerson(Context context, EPerson eperson, + Integer limit, Integer offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson)); - return list(context, criteriaQuery, false, Subscription.class, -1, -1); + return list(context, criteriaQuery, false, Subscription.class, limit, offset); } + @Override public List findByEPersonAndDso(Context context, EPerson eperson, - DSpaceObject dSpaceObject) throws SQLException { + DSpaceObject dSpaceObject, + Integer limit, Integer offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); @@ -56,10 +58,10 @@ public List findByEPersonAndDso(Context context, EPerson eperson, criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson)); criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal( - subscriptionRoot.get(Subscription_.ePerson), eperson), + subscriptionRoot.get(Subscription_.ePerson), eperson), criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) )); - return list(context, criteriaQuery, false, Subscription.class, -1, -1); + return list(context, criteriaQuery, false, Subscription.class, limit, offset); } @@ -89,6 +91,23 @@ public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EP query.executeUpdate(); } + @Override + public List findAllOrderedByEPersonAndResourceType(Context context, String resourceType, + Integer limit, Integer offset) throws SQLException { + String hqlQuery = "select s from Subscription s join %s dso ON dso.id = s.dSpaceObject ORDER BY eperson_id"; + if (resourceType != null) { + hqlQuery = String.format(hqlQuery, resourceType); + } + Query query = createQuery(context, hqlQuery); + if (limit != -1) { + query.setMaxResults(limit); + } + if (offset != -1) { + query.setFirstResult(offset); + } + query.setHint("org.hibernate.cacheable", false); + return query.getResultList(); + } @Override public List findAllOrderedByEPerson(Context context) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 9c763af4c96d..229c1fb9b45e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -2,7 +2,6 @@ * 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.eperson.service; @@ -33,10 +32,13 @@ public interface SubscribeService { * new item appears in the collection. * * @param context DSpace context + * @param limit Number of subscriptions to return + * @param offset Offset number * @return list of Subscription objects * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List findAll(Context context) throws SQLException; + public List findAll(Context context, String resourceType, + Integer limit, Integer offset) throws Exception; /** * Subscribe an e-person to a collection. An e-mail will be sent every day a @@ -72,10 +74,13 @@ public void unsubscribe(Context context, EPerson eperson, * * @param context DSpace context * @param eperson EPerson + * @param limit Number of subscriptions to return + * @param offset Offset number * @return array of collections e-person is subscribed to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getSubscriptionsByEPerson(Context context, EPerson eperson) throws SQLException; + public List getSubscriptionsByEPerson(Context context, EPerson eperson, + Integer limit, Integer offset) throws SQLException; /** * Find out which collections an e-person is subscribed to and related with dso @@ -83,12 +88,16 @@ public void unsubscribe(Context context, EPerson eperson, * @param context DSpace context * @param eperson EPerson * @param dSpaceObject DSpaceObject + * @param limit Number of subscriptions to return + * @param offset Offset number * @return array of collections e-person is subscribed to and related with dso * @throws SQLException An exception that provides information on a database access error or other errors. */ public List getSubscriptionsByEPersonAndDso(Context context, EPerson eperson, - DSpaceObject dSpaceObject) throws SQLException; + DSpaceObject dSpaceObject, + Integer limit, + Integer offset) throws SQLException; /** * Find out which collections the currently logged in e-person can subscribe to @@ -187,7 +196,7 @@ public Subscription addSubscriptionParameter(Context context, Integer id, * @throws SQLException An exception that provides information on a database access error or other errors. */ public Subscription removeSubscriptionParameter(Context context, Integer id, - SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; + SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; /** * Deletes a subscription diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 271ea533992a..ebac38f6270d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -90,10 +90,13 @@ public SubscriptionRest findOne(Context context, Integer id) { @PreAuthorize("hasAuthority('ADMIN')") public Page findAll(Context context, Pageable pageable) { try { - List subscriptionList = subscribeService.findAll(context); + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + String resourceType = req.getParameter("resourceType"); + List subscriptionList = subscribeService.findAll(context, resourceType, + pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); - } catch (SQLException sqlException) { - throw new RuntimeException(sqlException.getMessage(), sqlException); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); } } @@ -104,7 +107,8 @@ public Page findAllByEPerson(Context context, String id) { EPerson ePerson = personService.findByIdOrLegacyId(context, id); if (context.getCurrentUser().equals(ePerson) || authorizeService.isAdmin(context, context.getCurrentUser())) { - List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, ePerson); + List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, + ePerson, pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); } else { throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); @@ -130,7 +134,8 @@ public Page findByEPersonAndDso(Pageable pageable) throws Exce if (context.getCurrentUser().equals(ePerson) || authorizeService.isAdmin(context, context.getCurrentUser())) { List subscriptionList = - subscribeService.getSubscriptionsByEPersonAndDso(context, ePerson, dSpaceObject); + subscribeService.getSubscriptionsByEPersonAndDso(context, ePerson, dSpaceObject, + pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); return converter.toRestPage(subscriptionList, pageable, subscriptionList.size(), utils.obtainProjection()); } else { From 389cb760e22a5ef85f643485855a3f69b4b34359 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 1 Dec 2022 18:57:45 +0100 Subject: [PATCH 080/394] [DSC-193] tests added, code review fixes, other fixes in pagination and ordering --- .../dspace/content/CommunityServiceImpl.java | 8 +- .../org/dspace/eperson/SubscribeCLITool.java | 3 + .../dspace/eperson/SubscribeServiceImpl.java | 25 ++-- .../dspace/eperson/dao/SubscriptionDAO.java | 4 +- .../eperson/dao/impl/SubscriptionDAOImpl.java | 17 ++- .../eperson/service/SubscribeService.java | 2 +- ...ubscriptionDSpaceObjectLinkRepository.java | 5 +- .../SubscriptionEPersonLinkRepository.java | 6 +- .../SubscriptionRestRepository.java | 11 +- ...earcherProfileVisibleReplaceOperation.java | 2 +- .../rest/SubscriptionRestRepositoryIT.java | 112 ++++++++++++++++-- 11 files changed, 160 insertions(+), 35 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 6ff1784ac219..624fe74fe05e 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -219,15 +219,15 @@ public void setMetadataSingleValue(Context context, Community community, @Override public Bitstream setLogo(Context context, Community community, InputStream is) - throws AuthorizeException, IOException, SQLException { + throws AuthorizeException, IOException, SQLException { // Check authorisation // authorized to remove the logo when DELETE rights // authorized when canEdit if (!((is == null) && authorizeService.authorizeActionBoolean( - context, community, Constants.DELETE))) { + context, community, Constants.DELETE))) { canEdit(context, community); } - subscribeService.deleteByDspaceObject(context, community); + // First, delete any existing logo Bitstream oldLogo = community.getLogo(); if (oldLogo != null) { @@ -244,7 +244,7 @@ public Bitstream setLogo(Context context, Community community, InputStream is) // now create policy for logo bitstream // to match our READ policy List policies = authorizeService - .getPoliciesActionFilter(context, community, Constants.READ); + .getPoliciesActionFilter(context, community, Constants.READ); authorizeService.addPolicies(context, policies, newLogo); log.info(LogHelper.getHeader(context, "set_logo", diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java index b19ad78ff647..42841ea36b9f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java @@ -104,6 +104,9 @@ public static void processDaily(Context context, boolean test) throws SQLExcepti // Go through the list collating subscriptions for each e-person for (Subscription subscription : subscriptions) { + if (!(subscription.getdSpaceObject() != null && subscription.getdSpaceObject() instanceof Collection)) { + continue; + } // Does this row relate to the same e-person as the last? if ((currentEPerson == null) || (!subscription.getePerson().getID().equals(currentEPerson diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index f6f4a7953fa7..e71cff21bd35 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -49,10 +49,10 @@ protected SubscribeServiceImpl() { public List findAll(Context context, String resourceType, Integer limit, Integer offset) throws Exception { if (resourceType == null) { - return subscriptionDAO.findAllOrderedByEPerson(context); + return subscriptionDAO.findAllOrderedById(context, limit, offset); } else { if (resourceType.equals("Item") || resourceType.equals("Collection") || resourceType.equals("Community")) { - return subscriptionDAO.findAllOrderedByEPersonAndResourceType(context, resourceType, limit, offset); + return subscriptionDAO.findAllOrderedByIDAndResourceType(context, resourceType, limit, offset); } else { log.error("Resource type must be Item, Collection or Community"); throw new Exception("Resource type must be Item, Collection or Community"); @@ -69,13 +69,13 @@ public Subscription subscribe(Context context, EPerson eperson, if (authorizeService.isAdmin(context) || ((context.getCurrentUser() != null) && (context .getCurrentUser().getID().equals(eperson.getID())))) { - Subscription new_subscription = subscriptionDAO.create(context, new Subscription()); + Subscription newSubscription = subscriptionDAO.create(context, new Subscription()); subscriptionParameterList.forEach(subscriptionParameter -> - new_subscription.addParameter(subscriptionParameter)); - new_subscription.setePerson(eperson); - new_subscription.setdSpaceObject(dSpaceObject); - new_subscription.setType(type); - return new_subscription; + newSubscription.addParameter(subscriptionParameter)); + newSubscription.setePerson(eperson); + newSubscription.setdSpaceObject(dSpaceObject); + newSubscription.setType(type); + return newSubscription; } else { throw new AuthorizeException( "Only admin or e-person themselves can subscribe"); @@ -153,8 +153,13 @@ public void deleteByEPerson(Context context, EPerson ePerson) throws SQLExceptio } @Override - public Subscription findById(Context context, int id) throws SQLException { - return subscriptionDAO.findByID(context, Subscription.class, id); + public Subscription findById(Context context, int id) throws SQLException, AuthorizeException { + Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); + if (context.getCurrentUser().equals(subscription.getePerson()) || + authorizeService.isAdmin(context, context.getCurrentUser())) { + return subscription; + } + throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); } @Override diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index 92a06c98f67d..7494f90b870b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -39,8 +39,8 @@ public List findByEPersonAndDso(Context context, public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) throws SQLException; - public List findAllOrderedByEPersonAndResourceType(Context context, String resourceType, + public List findAllOrderedByIDAndResourceType(Context context, String resourceType, Integer limit, Integer offset) throws SQLException; - public List findAllOrderedByEPerson(Context context) throws SQLException; + public List findAllOrderedById(Context context, Integer limit, Integer offset) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index 7b7b292aeecc..72a65fad1996 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -42,6 +42,9 @@ public List findByEPerson(Context context, EPerson eperson, Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson)); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id))); + criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, Subscription.class, limit, offset); } @@ -61,6 +64,9 @@ public List findByEPersonAndDso(Context context, EPerson eperson, subscriptionRoot.get(Subscription_.ePerson), eperson), criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) )); + List orderList = new LinkedList<>(); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id))); + criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, Subscription.class, limit, offset); } @@ -92,9 +98,10 @@ public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EP } @Override - public List findAllOrderedByEPersonAndResourceType(Context context, String resourceType, + public List findAllOrderedByIDAndResourceType(Context context, String resourceType, Integer limit, Integer offset) throws SQLException { - String hqlQuery = "select s from Subscription s join %s dso ON dso.id = s.dSpaceObject ORDER BY eperson_id"; + String hqlQuery = "select s from Subscription s join %s dso " + + "ON dso.id = s.dSpaceObject ORDER BY subscription_id"; if (resourceType != null) { hqlQuery = String.format(hqlQuery, resourceType); } @@ -109,14 +116,14 @@ public List findAllOrderedByEPersonAndResourceType(Context context return query.getResultList(); } @Override - public List findAllOrderedByEPerson(Context context) throws SQLException { + public List findAllOrderedById(Context context, Integer limit, Integer offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); List orderList = new LinkedList<>(); - orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.ePerson))); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id))); criteriaQuery.orderBy(orderList); - return list(context, criteriaQuery, false, Subscription.class, -1, -1); + return list(context, criteriaQuery, false, Subscription.class, limit, offset); } } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 229c1fb9b45e..8327e78a0477 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -157,7 +157,7 @@ public boolean isSubscribed(Context context, EPerson eperson, * @param id the id of subscription to be searched * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription findById(Context context, int id) throws SQLException; + public Subscription findById(Context context, int id) throws SQLException, AuthorizeException; /** * Updates a subscription by id diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index b36a7509977b..ffd2d606dd3b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -14,6 +14,7 @@ import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.SubscribeService; @@ -38,7 +39,7 @@ public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRe public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, Integer subscriptionId, @Nullable Pageable optionalPageable, - Projection projection) { + Projection projection) throws AuthorizeException { try { Context context = obtainContext(); Subscription subscription = subscribeService.findById(context, subscriptionId); @@ -51,6 +52,8 @@ public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, return converter.toRest(initializer.getImplementation(), projection); } catch (SQLException e) { throw new RuntimeException(e); + } catch (AuthorizeException e) { + throw new AuthorizeException(e.getMessage()); } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index 2e24db95967e..b1cbc5713581 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -14,6 +14,7 @@ import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.SubscribeService; @@ -34,7 +35,7 @@ public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestReposit public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subscriptionId, @Nullable Pageable optionalPageable, - Projection projection) { + Projection projection) throws AuthorizeException { try { Context context = obtainContext(); Subscription subscription = subscribeService.findById(context, subscriptionId); @@ -46,5 +47,8 @@ public EPersonRest getEPerson(@Nullable HttpServletRequest request, } catch (SQLException e) { throw new RuntimeException(e); } + catch (AuthorizeException e) { + throw new AuthorizeException(e.getMessage()); + } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index ebac38f6270d..6c2e49c02266 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -83,6 +83,8 @@ public SubscriptionRest findOne(Context context, Integer id) { return converter.toRest(subscription, utils.obtainProjection()); } catch (SQLException sqlException) { throw new RuntimeException(sqlException.getMessage(), sqlException); + } catch (AuthorizeException authorizeException) { + throw new RuntimeException(authorizeException.getMessage()); } } @@ -96,7 +98,7 @@ public Page findAll(Context context, Pageable pageable) { pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); + throw new RuntimeException(e.getMessage()); } } @@ -205,6 +207,8 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri } } catch (SQLException e) { throw new ResourceNotFoundException(notFoundException); + } catch (AuthorizeException e) { + throw new AuthorizeException(e.getMessage()); } if (id.equals(subscription.getID())) { List subscriptionParameterList = new ArrayList<>(); @@ -218,6 +222,7 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri } subscription = subscribeService.updateSubscription(context, id, ePerson, dSpaceObject, subscriptionParameterList, subscriptionRest.getSubscriptionType()); + context.commit(); return converter.toRest(subscription, utils.obtainProjection()); } else { throw new IllegalArgumentException("The id in the Json and the id in the url do not match: " @@ -240,6 +245,10 @@ public void patch(Context context, HttpServletRequest request, String apiCategor resourcePatch.patch(context, subscription, patch.getOperations()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); + } catch (AuthorizeException authorizeException) { + throw new AuthorizeException(authorizeException.getMessage()); + } catch (RuntimeException runtimeException) { + throw new RuntimeException(runtimeException.getMessage()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.java index 5ab8cd88a45b..3e3c0c82f204 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileVisibleReplaceOperation.java @@ -21,7 +21,7 @@ /** * Implementation for ResearcherProfile visibility patches. - *SubscriptionParameterAddOperationpatches + * * Example: * curl -X PATCH http://${dspace.server.url}/api/eperson/profiles/<:id-eperson> -H " * Content-Type: application/json" -d '[{ "op": "replace", "path": " diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index eccfb647a827..1422ec55a251 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -80,13 +80,10 @@ public void setUp() throws Exception { context.restoreAuthSystemState(); } + // FIND ALL @Test public void findAll() throws Exception { context.turnOffAuthorisationSystem(); - //When we call the root endpoint as anonymous user - getClient().perform(get("/api/core/subscriptions")) - //The status has to be 401 Not Authorized - .andExpect(status().isUnauthorized()); String token = getAuthToken(admin.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); @@ -118,15 +115,80 @@ public void findAll() throws Exception { } @Test - public void findByIdAsAdministrator() throws Exception { + public void findAllAnonymous() throws Exception { + //When we call the root endpoint as anonymous user + getClient().perform(get("/api/core/subscriptions")) + //The status has to be 401 Not Authorized + .andExpect(status().isUnauthorized()); + } + + @Test + public void findAllAsUser() throws Exception { + context.turnOffAuthorisationSystem(); + String token = getAuthToken(eperson.getEmail(), password); + context.restoreAuthSystemState(); + //When we call the root endpoint as simple user + getClient(token).perform(get("/api/core/subscriptions")) + //The status has to be 403 Forbidden + .andExpect(status().isForbidden()); + } + + @Test + public void findAllWithResourceType() throws Exception { context.turnOffAuthorisationSystem(); //When we call the root endpoint as anonymous user getClient().perform(get("/api/core/subscriptions")) - //The status has to be 403 Not Authorized + //The status has to be 401 Not Authorized .andExpect(status().isUnauthorized()); String token = getAuthToken(admin.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Frequency"); + subscriptionParameter.setValue("Daily"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TypeTest", publicItem, admin, subscriptionParameterList).build(); + subscriptionParameter.setSubscription(subscription); + //When we call the root endpoint + context.restoreAuthSystemState(); + getClient(token).perform(get("/api/core/subscriptions?resourceType=Item")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) + .andExpect(jsonPath("$._links.self.href", Matchers.is(REST_SERVER_URL + "core/subscriptions?resourceType=Item"))); + // search for subscriptions related with collections + getClient(token).perform(get("/api/core/subscriptions?resourceType=Collection")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(0))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(0))) + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$._links.self.href", Matchers.is(REST_SERVER_URL + "core/subscriptions?resourceType=Collection"))); + } + + // FIND BY ID + @Test + public void findByIdAsAdministrator() throws Exception { + context.turnOffAuthorisationSystem(); + String token = getAuthToken(admin.getEmail(), password); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter"); subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); @@ -167,6 +229,24 @@ public void findByIdAsAnonymous() throws Exception { .andExpect(status().isUnauthorized()); } + @Test + public void findByIdNotAsSubscriberNotAsAdmin() throws Exception { + context.turnOffAuthorisationSystem(); + String token = getAuthToken(eperson.getEmail(), password); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + //When we call the root endpoint + getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) + //The status has to be 500 + .andExpect(status().isInternalServerError()); + } + + // FIND ALL BY EPERSON/DSO @Test public void findAllSubscriptionsByEPerson() throws Exception { //When we call the root endpoint as anonymous user @@ -228,6 +308,7 @@ public void findAllSubscriptionsByEPerson() throws Exception { .andExpect(status().isUnauthorized()); } + // ADD @Test public void addSubscriptionNotLoggedIn() throws Exception { SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); @@ -287,6 +368,7 @@ public void addSubscriptionAsAdmin() throws Exception { .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("ePerson"))); } + // PUT @Test public void editSubscriptionAnonymous() throws Exception { context.turnOffAuthorisationSystem(); @@ -330,12 +412,22 @@ public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); + ObjectMapper objectMapper = new ObjectMapper(); + String token = getAuthToken(epersonIT.getEmail(), password); + Map newSubscription = new HashMap<>(); + newSubscription.put("type", "test"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "daily"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); //When we call the root endpoint as anonymous user - getClient().perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + admin.getID()) + getClient(token).perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + admin.getID()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) - //The status has to be 403 Not Authorized - .andExpect(status().isUnauthorized()); + //The status has to be 500 Error + .andExpect(status().isInternalServerError()); } @Test @@ -378,6 +470,7 @@ public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); } + // DELETE @Test public void deleteSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); @@ -416,6 +509,7 @@ public void deleteSubscriptionAsAdmin() throws Exception { .andExpect(status().isOk()); } + // PATCH @Test public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); From d08b3cb984d9d23281a8e20506d6df4755d695ae Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 1 Dec 2022 18:58:42 +0100 Subject: [PATCH 081/394] [DSC-183] header licence --- .../dspace/eperson/SubscribeServiceImpl.java | 1 + .../java/org/dspace/eperson/Subscription.java | 1 + .../dspace/eperson/SubscriptionParameter.java | 1 + .../dspace/eperson/dao/SubscriptionDAO.java | 1 + .../eperson/dao/impl/SubscriptionDAOImpl.java | 1 + .../eperson/service/SubscribeService.java | 1 + .../service/SubscriptionParameterService.java | 2 +- .../org/dspace/builder/SubscribeBuilder.java | 104 ++++++++++++++++++ .../rest/model/SubscriptionParameterRest.java | 52 +++++++++ 9 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index e71cff21bd35..295944df00bd 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -2,6 +2,7 @@ * 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.eperson; diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index d7090ed7ddad..48dd24cdd452 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -2,6 +2,7 @@ * 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.eperson; diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java index 04acbf7bbf71..0b69b365155b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java @@ -2,6 +2,7 @@ * 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.eperson; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index 7494f90b870b..4f4ed16a2e41 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -2,6 +2,7 @@ * 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.eperson.dao; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index 72a65fad1996..043fd70b57b3 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -2,6 +2,7 @@ * 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.eperson.dao.impl; diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 8327e78a0477..607118ce5619 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -2,6 +2,7 @@ * 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.eperson.service; diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java index eda5d6c570f8..88052ac20a43 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java @@ -2,7 +2,7 @@ * 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.eperson.service; diff --git a/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java new file mode 100644 index 000000000000..1d714b856708 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java @@ -0,0 +1,104 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.builder; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.service.SubscribeService; + +import java.sql.SQLException; +import java.util.List; + +public class SubscribeBuilder extends AbstractBuilder { + + /* Log4j logger*/ + private static final Logger log = LogManager.getLogger(); + + private Subscription subscription; + + protected SubscribeBuilder(Context context) { + super(context); + } + + @Override + protected SubscribeService getService() { + return subscribeService; + } + + @Override + public void cleanup() throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + // Ensure object and any related objects are reloaded before checking to see what needs cleanup + subscription = c.reloadEntity(subscription); + if (subscription != null) { + delete(subscription); + } + c.complete(); + indexingService.commit(); + } + } + + @Override + public void delete(Context c, Subscription subscription) throws Exception { + if (subscription != null) { + getService().deleteSubscription(c, subscription.getID()); + } + } + + @Override + public Subscription build() { + try { + + context.dispatchEvents(); + + indexingService.commit(); + } catch (SearchServiceException e) { + log.error(e); + } + return subscription; + } + + public void delete(Subscription subscription) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Subscription subscription1 = c.reloadEntity(subscription); + if (subscription1 != null) { + getService().deleteSubscription(c, subscription1.getID()); + } + c.complete(); + } + + indexingService.commit(); + } + + public static SubscribeBuilder subscribeBuilder(final Context context, String type, DSpaceObject dSpaceObject, EPerson ePerson, List subscriptionParameterList) { + SubscribeBuilder builder = new SubscribeBuilder(context); + return builder.create(context, type, dSpaceObject, ePerson, subscriptionParameterList); + } + + private SubscribeBuilder create(Context context, String type, DSpaceObject dSpaceObject, EPerson ePerson, List subscriptionParameterList) { + try { + + this.context = context; + this.subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, + subscriptionParameterList, type); + + } catch (SQLException | AuthorizeException e) { + log.warn("Failed to create the Subscription", e); + } + return this; + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java new file mode 100644 index 000000000000..71e3c47fb73b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java @@ -0,0 +1,52 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.rest.model; + + +import org.dspace.eperson.Subscription; + +public class SubscriptionParameterRest { + private Integer id; + private String name; + private String value; + + public SubscriptionParameterRest() { + } + + public SubscriptionParameterRest(Integer id, String name, String value, Subscription subscription) { + this.id = id; + this.name = name; + this.value = value; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} From 31cb37b4a5db81894f0b754550082141910d233c Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 2 Dec 2022 09:49:06 +0100 Subject: [PATCH 082/394] 97183 Minor fixes to IndexingUtils#findDirectlyAuthorizedGroupAndEPersonPrefixedIds --- .../org/dspace/discovery/IndexingUtils.java | 29 +++++++++---------- ...erviceIndexCollectionSubmittersPlugin.java | 14 ++++++--- .../SolrServiceIndexItemEditorsPlugin.java | 11 ++++--- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java index 35835ec402c3..496cfd8c83cb 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java @@ -14,6 +14,7 @@ import java.util.UUID; import java.util.stream.Stream; +import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -89,12 +90,6 @@ static Stream findTransitiveAdminGroupIds(Context context, Collection coll * (through direct resource policy) or indirectly (through a policy on the owning collection, or on * the owning collection's community, or on any of that community's ancestor communities). * - * @param authService - * @param context - * @param item - * @return - * @throws SQLException - * * @param authService The authentication service * @param context DSpace context object * @param item Item for which we search the admin group IDs @@ -126,18 +121,20 @@ static Stream findTransitiveAdminGroupIds(AuthorizeService authService, Co * group or eperson ID. * @throws SQLException if database error */ - static Stream findDirectAuthorizedGroupsAndEPersonsPrefixedIds(AuthorizeService authService, - Context context, DSpaceObject obj, int[] authorizations) throws SQLException { - - ArrayList> subResults = new ArrayList<>(); + static List findDirectlyAuthorizedGroupAndEPersonPrefixedIds( + AuthorizeService authService, Context context, DSpaceObject obj, int[] authorizations) + throws SQLException { + ArrayList prefixedIds = new ArrayList<>(); for (int auth : authorizations) { - Stream subResult = authService.getPoliciesActionFilter(context, obj, auth).stream() - .map(policy -> policy.getGroup() == null ? "e" + policy.getEPerson().getID() - : "g" + policy.getGroup().getID()); - subResults.add(subResult); - // TODO: context.uncacheEntitiy(policy); + for (ResourcePolicy policy : authService.getPoliciesActionFilter(context, obj, auth)) { + String prefixedId = policy.getGroup() == null + ? "e" + policy.getEPerson().getID() + : "g" + policy.getGroup().getID(); + prefixedIds.add(prefixedId); + context.uncacheEntity(policy); + } } - return sequence(subResults); + return prefixedIds; } private static Stream sequence(List> subResults) { diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java index 5b97c1edfe03..bcd0f24cd388 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java @@ -7,7 +7,11 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.IndexingUtils.findDirectlyAuthorizedGroupAndEPersonPrefixedIds; +import static org.dspace.discovery.IndexingUtils.findTransitiveAdminGroupIds; + import java.sql.SQLException; +import java.util.List; import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrInputDocument; @@ -43,13 +47,16 @@ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo // Community. // TODO: Strictly speaking we should also check for epersons who received admin rights directly, // without being part of the admin group. Finding them may be a lot slower though. - IndexingUtils.findTransitiveAdminGroupIds(context, col) + findTransitiveAdminGroupIds(context, col) .forEach(unprefixedId -> document.addField("submit", "g" + unprefixedId)); // Index groups and epersons with ADD rights on the Collection. - IndexingUtils.findDirectAuthorizedGroupsAndEPersonsPrefixedIds( + List prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds( authorizeService, context, col, new int[] {Constants.ADD} - ).forEach(prefixedId -> document.addField("submit", prefixedId)); + ); + for (String prefixedId : prefixedIds) { + document.addField("submit", prefixedId); + } } catch (SQLException e) { log.error(LogHelper.getHeader(context, "Error while indexing resource policies", "Collection: (id " + col.getID() + " type " + col.getName() + ")" )); @@ -57,5 +64,4 @@ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo } } } - } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java index d4b253851939..b404c647c515 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java @@ -7,10 +7,11 @@ */ package org.dspace.discovery; -import static org.dspace.discovery.IndexingUtils.findDirectAuthorizedGroupsAndEPersonsPrefixedIds; +import static org.dspace.discovery.IndexingUtils.findDirectlyAuthorizedGroupAndEPersonPrefixedIds; import static org.dspace.discovery.IndexingUtils.findTransitiveAdminGroupIds; import java.sql.SQLException; +import java.util.List; import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrInputDocument; @@ -49,10 +50,12 @@ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo .forEach(unprefixedId -> document.addField("edit", "g" + unprefixedId)); // Index groups and epersons with WRITE rights on the Item. - findDirectAuthorizedGroupsAndEPersonsPrefixedIds( + List prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds( authorizeService, context, item, new int[] {Constants.WRITE} - ).forEach(prefixedId -> document.addField("edit", prefixedId)); - + ); + for (String prefixedId : prefixedIds) { + document.addField("edit", prefixedId); + } } catch (SQLException e) { log.error(LogHelper.getHeader(context, "Error while indexing resource policies", "Item: (id " + item.getID() + " name " + item.getName() + ")" )); From f99e8763276ac0cbe46f373ed78b59a8aef0bbb9 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 2 Dec 2022 10:50:24 +0100 Subject: [PATCH 083/394] 97183 IndexingUtils: Changed interface to Lists instead of Streams --- .../org/dspace/discovery/IndexingUtils.java | 79 +++++++------------ ...erviceIndexCollectionSubmittersPlugin.java | 10 ++- .../SolrServiceIndexItemEditorsPlugin.java | 15 ++-- 3 files changed, 43 insertions(+), 61 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java index 496cfd8c83cb..2127c7557d29 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java @@ -12,16 +12,14 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; -import java.util.stream.Stream; +import java.util.stream.Collectors; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.core.Constants; import org.dspace.core.Context; /** @@ -29,27 +27,30 @@ * * @author Koen Pauwels (koen.pauwels at atmire dot com) */ -public abstract class IndexingUtils { +public class IndexingUtils { + private IndexingUtils() { + } + /** * Retrieve all ancestor communities of a given community, with the first one being the given community and the * last one being the root. - * + *

* TODO: can be done in a single SQL query with recursive common table expressions * TODO: should probably be moved to CommunityService * * @param context DSpace context object * @param community Community for which we search the ancestors - * @return A stream of ancestor communities. + * @return A list of ancestor communities. * @throws SQLException if database error */ - static Stream getAncestorCommunities(Context context, Community community) throws SQLException { + static List getAncestorCommunities(Context context, Community community) throws SQLException { ArrayList communities = new ArrayList<>(); while (community != null) { communities.add(community); community = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(community) .getParentObject(context, community); } - return communities.stream(); + return communities; } /** @@ -58,12 +59,13 @@ static Stream getAncestorCommunities(Context context, Community commu * * @param context DSpace context object * @param community Community for which we search the admin group IDs - * @return A stream of admin group IDs + * @return A list of admin group IDs * @throws SQLException if database error */ - static Stream findTransitiveAdminGroupIds(Context context, Community community) throws SQLException { - return getAncestorCommunities(context, community) - .map(parent -> parent.getAdministrators().getID()); + static List findTransitiveAdminGroupIds(Context context, Community community) throws SQLException { + return getAncestorCommunities(context, community).stream() + .map(parent -> parent.getAdministrators().getID()) + .collect(Collectors.toList()); } /** @@ -71,42 +73,19 @@ static Stream findTransitiveAdminGroupIds(Context context, Community commu * (through direct resource policy) or indirectly (through a policy on its community, or one of * its ancestor communities). * - * @param context DSpace context object + * @param context DSpace context object * @param collection Collection for which we search the admin group IDs - * @return A stream of admin group IDs + * @return A list of admin group IDs * @throws SQLException if database error */ - static Stream findTransitiveAdminGroupIds(Context context, Collection collection) throws SQLException { - UUID directAdminGroupId = collection.getAdministrators().getID(); - List> subResults = Arrays.asList(Stream.of(directAdminGroupId)); + static List findTransitiveAdminGroupIds(Context context, Collection collection) throws SQLException { + List ids = Arrays.asList(collection.getAdministrators().getID()); for (Community community : collection.getCommunities()) { - subResults.add(findTransitiveAdminGroupIds(context, community)); - } - return sequence(subResults); - } - - /** - * Retrieve the ids of all groups that have ADMIN rights on the given item, either directly - * (through direct resource policy) or indirectly (through a policy on the owning collection, or on - * the owning collection's community, or on any of that community's ancestor communities). - * - * @param authService The authentication service - * @param context DSpace context object - * @param item Item for which we search the admin group IDs - * @return A stream of admin group IDs - * @throws SQLException if database error - */ - static Stream findTransitiveAdminGroupIds(AuthorizeService authService, Context context, Item item) - throws SQLException { - Stream directAdminGroupIds = authService.getPoliciesActionFilter(context, item, Constants.ADMIN) - .stream() - .filter(policy -> policy.getGroup() != null) - .map(policy -> policy.getGroup().getID()); - List> subResults = Arrays.asList(directAdminGroupIds); - for (Collection coll : item.getCollections()) { - subResults.add(findTransitiveAdminGroupIds(context, coll)); + for (UUID id : findTransitiveAdminGroupIds(context, community)) { + ids.add(id); + } } - return sequence(subResults); + return ids; } /** @@ -114,11 +93,11 @@ static Stream findTransitiveAdminGroupIds(AuthorizeService authService, Co * on the given DSpaceObject. The resulting IDs are prefixed with "e" in the case of an eperson ID, and "g" in the * case of a group ID. * - * @param authService The authentication service - * @param context DSpace context object - * @param obj DSpaceObject for which we search the admin group IDs - * @return A stream of admin group IDs as Strings, prefixed with either "e" or "g", depending on whether it is a - * group or eperson ID. + * @param authService The authentication service + * @param context DSpace context object + * @param obj DSpaceObject for which we search the admin group IDs + * @return A stream of admin group IDs as Strings, prefixed with either "e" or "g", depending on whether it is a + * group or eperson ID. * @throws SQLException if database error */ static List findDirectlyAuthorizedGroupAndEPersonPrefixedIds( @@ -136,8 +115,4 @@ static List findDirectlyAuthorizedGroupAndEPersonPrefixedIds( } return prefixedIds; } - - private static Stream sequence(List> subResults) { - return subResults.stream().flatMap(x -> x); - } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java index bcd0f24cd388..ee93f954a5bd 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java @@ -12,6 +12,7 @@ import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrInputDocument; @@ -47,12 +48,13 @@ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo // Community. // TODO: Strictly speaking we should also check for epersons who received admin rights directly, // without being part of the admin group. Finding them may be a lot slower though. - findTransitiveAdminGroupIds(context, col) - .forEach(unprefixedId -> document.addField("submit", "g" + unprefixedId)); + for (UUID unprefixedId : findTransitiveAdminGroupIds(context, col)) { + document.addField("submit", "g" + unprefixedId); + } - // Index groups and epersons with ADD rights on the Collection. + // Index groups and epersons with ADD or ADMIN rights on the Collection. List prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds( - authorizeService, context, col, new int[] {Constants.ADD} + authorizeService, context, col, new int[] {Constants.ADD, Constants.ADMIN} ); for (String prefixedId : prefixedIds) { document.addField("submit", prefixedId); diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java index b404c647c515..b10d2d72a332 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java @@ -12,10 +12,12 @@ import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.apache.logging.log4j.Logger; import org.apache.solr.common.SolrInputDocument; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -41,17 +43,20 @@ public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDo Item item = ((IndexableItem) idxObj).getIndexedObject(); if (item != null) { try { - // Index groups with ADMIN rights on the Item, on Collections containing the Item, on + // Index groups with ADMIN rights on Collections containing the Item, on // Communities containing those Collections, and recursively on any Community containing ssuch a // Community. // TODO: Strictly speaking we should also check for epersons who received admin rights directly, // without being part of the admin group. Finding them may be a lot slower though. - findTransitiveAdminGroupIds(authorizeService, context, item) - .forEach(unprefixedId -> document.addField("edit", "g" + unprefixedId)); + for (Collection collection : item.getCollections()) { + for (UUID unprefixedId : findTransitiveAdminGroupIds(context, collection)) { + document.addField("edit", "g" + unprefixedId); + } + } - // Index groups and epersons with WRITE rights on the Item. + // Index groups and epersons with WRITE or direct ADMIN rights on the Item. List prefixedIds = findDirectlyAuthorizedGroupAndEPersonPrefixedIds( - authorizeService, context, item, new int[] {Constants.WRITE} + authorizeService, context, item, new int[] {Constants.WRITE, Constants.ADMIN} ); for (String prefixedId : prefixedIds) { document.addField("edit", prefixedId); From 6cbb1630e3280995242e9c49858e6ccb7876018f Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 2 Dec 2022 16:08:35 +0100 Subject: [PATCH 084/394] 97183 ItemService: added methods to search the index for items for which the current user has editing rights --- .../org/dspace/content/ItemServiceImpl.java | 55 +++++++++++++++++++ .../dspace/content/service/ItemService.java | 22 ++++++++ .../org/dspace/discovery/IndexingUtils.java | 7 ++- dspace/config/spring/api/discovery.xml | 1 + 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 27d0ba189cbd..156c8b271237 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -51,8 +51,14 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogHelper; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; import org.dspace.event.Event; import org.dspace.harvest.HarvestedItem; import org.dspace.harvest.service.HarvestedItemService; @@ -93,6 +99,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) protected CommunityService communityService; @Autowired(required = true) + protected GroupService groupService; + @Autowired(required = true) protected AuthorizeService authorizeService; @Autowired(required = true) protected BundleService bundleService; @@ -105,6 +113,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) protected InstallItemService installItemService; @Autowired(required = true) + protected SearchService searchService; + @Autowired(required = true) protected ResourcePolicyService resourcePolicyService; @Autowired(required = true) protected CollectionService collectionService; @@ -1065,6 +1075,51 @@ public boolean canEdit(Context context, Item item) throws SQLException { return collectionService.canEditBoolean(context, item.getOwningCollection(), false); } + /** + * Finds all Indexed Items where the current user has submit rights. If the user is an Admin, + * this is all Indexed Items. Otherwise, it includes those Items where + * an indexed "submit" policy lists either the eperson or one of the eperson's groups + * + * @param context DSpace context + * @param discoverQuery + * @return discovery search result objects + * @throws SQLException if something goes wrong + * @throws SearchServiceException if search error + */ + private DiscoverResult retrieveItemsWithEdit(Context context, DiscoverQuery discoverQuery) + throws SQLException, SearchServiceException { + EPerson currentUser = context.getCurrentUser(); + if (!authorizeService.isAdmin(context)) { + String userId = currentUser != null ? "e" + currentUser.getID().toString() : "e"; + Stream groupIds = groupService.allMemberGroupsSet(context, currentUser).stream() + .map(group -> "g" + group.getID()); + String query = Stream.concat(Stream.of(userId), groupIds) + .collect(Collectors.joining(" OR ", "edit:(", ")")); + discoverQuery.addFilterQueries(query); + } + return searchService.search(context, discoverQuery); + } + + public List findItemsWithEdit(Context context, int offset, int limit) + throws SQLException, SearchServiceException { + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE); + discoverQuery.setStart(offset); + discoverQuery.setMaxResults(limit); + DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery); + return resp.getIndexableObjects().stream() + .map(solrItems -> ((IndexableItem) solrItems).getIndexedObject()) + .collect(Collectors.toList()); + } + + public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException { + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setMaxResults(0); + discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE); + DiscoverResult resp = retrieveItemsWithEdit(context, discoverQuery); + return (int) resp.getTotalSearchResults(); + } + /** * Check if the item is an inprogress submission * diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index 8b7badf22385..b7a479469bf1 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -28,6 +28,7 @@ import org.dspace.content.Thumbnail; import org.dspace.content.WorkspaceItem; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -768,6 +769,27 @@ public Iterator findByLastModifiedSince(Context context, Date last) */ int countWithdrawnItems(Context context) throws SQLException; + /** + * finds all items for which the current user has editing rights + * @param context DSpace context object + * @param offset page offset + * @param limit page size limit + * @return list of items for which the current user has editing rights + * @throws SQLException + * @throws SearchServiceException + */ + public List findItemsWithEdit(Context context, int offset, int limit) + throws SQLException, SearchServiceException; + + /** + * counts all items for which the current user has editing rights + * @param context DSpace context object + * @return list of items for which the current user has editing rights + * @throws SQLException + * @throws SearchServiceException + */ + public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException; + /** * Check if the supplied item is an inprogress submission * diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java index 2127c7557d29..c4848c743292 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java @@ -9,7 +9,6 @@ import java.sql.SQLException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -64,6 +63,7 @@ static List getAncestorCommunities(Context context, Community communi */ static List findTransitiveAdminGroupIds(Context context, Community community) throws SQLException { return getAncestorCommunities(context, community).stream() + .filter(parent -> parent.getAdministrators() != null) .map(parent -> parent.getAdministrators().getID()) .collect(Collectors.toList()); } @@ -79,7 +79,10 @@ static List findTransitiveAdminGroupIds(Context context, Community communi * @throws SQLException if database error */ static List findTransitiveAdminGroupIds(Context context, Collection collection) throws SQLException { - List ids = Arrays.asList(collection.getAdministrators().getID()); + List ids = new ArrayList<>(); + if (collection.getAdministrators() != null) { + ids.add(collection.getAdministrators().getID()); + } for (Community community : collection.getCommunities()) { for (UUID id : findTransitiveAdminGroupIds(context, community)) { ids.add(id); diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 57f4c07aee5c..188f417059b7 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -31,6 +31,7 @@ + From 2c11f7c757dcaffdfb5a8431480b31cede96d3bf Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 2 Dec 2022 18:19:37 +0100 Subject: [PATCH 085/394] [CST-7756] refactoring --- .../factory/ContentServiceFactory.java | 3 + .../factory/ContentServiceFactoryImpl.java | 9 +- .../FrequencySubscriptionParameter.java | 12 - .../SubscribeParameterServiceImpl.java | 12 +- .../dspace/eperson/SubscribeServiceImpl.java | 88 ++-- .../java/org/dspace/eperson/Subscription.java | 6 +- .../dspace/eperson/SubscriptionParameter.java | 29 +- .../dspace/eperson/dao/SubscriptionDAO.java | 21 +- .../eperson/dao/impl/SubscriptionDAOImpl.java | 74 ++- .../impl/SubscriptionParameterDAOImpl.java | 6 +- .../eperson/service/SubscribeService.java | 80 +++- ...mter_change_columns_subscription_table.sql | 2 +- .../org/dspace/builder/AbstractBuilder.java | 5 +- .../org/dspace/builder/SubscribeBuilder.java | 14 +- .../rest/converter/SubscriptionConverter.java | 15 +- .../app/rest/model/SubscriptionRest.java | 14 +- ...ubscriptionDSpaceObjectLinkRepository.java | 33 +- .../SubscriptionEPersonLinkRepository.java | 19 +- .../SubscriptionRestRepository.java | 129 ++++-- ...SubscriptionParameterReplaceOperation.java | 21 +- .../rest/SubscriptionRestRepositoryIT.java | 435 +++++++++--------- .../config/spring/api/core-dao-services.xml | 1 + dspace/config/spring/api/core-services.xml | 1 + 23 files changed, 607 insertions(+), 422 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/eperson/FrequencySubscriptionParameter.java diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java index 4010e148610c..2335af007ff2 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java @@ -33,6 +33,7 @@ import org.dspace.content.service.SiteService; import org.dspace.content.service.SupervisedItemService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.service.SubscribeService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -75,6 +76,8 @@ public abstract class ContentServiceFactory { public abstract SiteService getSiteService(); + public abstract SubscribeService getSubscribeService(); + /** * Return the implementation of the RelationshipTypeService interface * diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java index 6f123ae1bac7..05eabcccdc2b 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java @@ -30,6 +30,7 @@ import org.dspace.content.service.SiteService; import org.dspace.content.service.SupervisedItemService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.service.SubscribeService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -71,7 +72,8 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory { private SupervisedItemService supervisedItemService; @Autowired(required = true) private SiteService siteService; - + @Autowired(required = true) + private SubscribeService subscribeService; @Autowired(required = true) private RelationshipService relationshipService; @Autowired(required = true) @@ -158,6 +160,11 @@ public SiteService getSiteService() { return siteService; } + @Override + public SubscribeService getSubscribeService() { + return subscribeService ; + } + @Override public RelationshipTypeService getRelationshipTypeService() { return relationshipTypeService; diff --git a/dspace-api/src/main/java/org/dspace/eperson/FrequencySubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/FrequencySubscriptionParameter.java deleted file mode 100644 index e735c969482f..000000000000 --- a/dspace-api/src/main/java/org/dspace/eperson/FrequencySubscriptionParameter.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.dspace.eperson; - -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -/** - * @author Alba Aliu (alba.aliu at alba.aliu@atis.al) - * - */ -@Entity -@DiscriminatorValue("FREQUENCY") -public class FrequencySubscriptionParameter extends SubscriptionParameter { -} diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java index 88842ebfa95b..870a95b7f766 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java @@ -10,32 +10,23 @@ import java.sql.SQLException; import java.util.List; -import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.dao.SubscriptionParameterDAO; import org.dspace.eperson.service.SubscriptionParameterService; import org.springframework.beans.factory.annotation.Autowired; - /** * Class implemennting method for service layer of SubscriptionParameter entity * * @author Alba Aliu at atis.al */ public class SubscribeParameterServiceImpl implements SubscriptionParameterService { - /** - * log4j logger - */ - private Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeParameterServiceImpl.class); @Autowired(required = true) protected SubscriptionParameterDAO subscriptionParameterDAO; - - protected SubscribeParameterServiceImpl() { - - } + protected SubscribeParameterServiceImpl() {} @Override public List findAll(Context context) throws SQLException { @@ -82,4 +73,5 @@ public void deleteSubscriptionParameter(Context context, Integer id) throws SQLE } } + } diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 295944df00bd..817085cdda04 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -9,7 +9,9 @@ import java.sql.SQLException; import java.util.List; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -30,27 +32,21 @@ * @version $Revision$ */ public class SubscribeServiceImpl implements SubscribeService { - /** - * log4j logger - */ - private Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeServiceImpl.class); + + private Logger log = LogManager.getLogger(SubscribeServiceImpl.class); @Autowired(required = true) - protected SubscriptionDAO subscriptionDAO; + private SubscriptionDAO subscriptionDAO; @Autowired(required = true) - protected AuthorizeService authorizeService; + private AuthorizeService authorizeService; @Autowired(required = true) - protected CollectionService collectionService; - - protected SubscribeServiceImpl() { - - } + private CollectionService collectionService; @Override public List findAll(Context context, String resourceType, Integer limit, Integer offset) throws Exception { if (resourceType == null) { - return subscriptionDAO.findAllOrderedById(context, limit, offset); + return subscriptionDAO.findAllOrderedByDSO(context, limit, offset); } else { if (resourceType.equals("Item") || resourceType.equals("Collection") || resourceType.equals("Community")) { return subscriptionDAO.findAllOrderedByIDAndResourceType(context, resourceType, limit, offset); @@ -78,14 +74,13 @@ public Subscription subscribe(Context context, EPerson eperson, newSubscription.setType(type); return newSubscription; } else { - throw new AuthorizeException( - "Only admin or e-person themselves can subscribe"); + throw new AuthorizeException("Only admin or e-person themselves can subscribe"); } } @Override - public void unsubscribe(Context context, EPerson eperson, - DSpaceObject dSpaceObject) throws SQLException, AuthorizeException { + public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject) + throws SQLException, AuthorizeException { // Check authorisation. Must be administrator, or the eperson. if (authorizeService.isAdmin(context) || ((context.getCurrentUser() != null) && (context @@ -96,13 +91,12 @@ public void unsubscribe(Context context, EPerson eperson, } else { subscriptionDAO.deleteByDSOAndEPerson(context, dSpaceObject, eperson); - log.info(LogManager.getHeader(context, "unsubscribe", - "eperson_id=" + eperson.getID() + ",collection_id=" - + dSpaceObject.getID())); + log.info(LogHelper.getHeader(context, "unsubscribe", + "eperson_id=" + eperson.getID() + ",collection_id=" + + dSpaceObject.getID())); } } else { - throw new AuthorizeException( - "Only admin or e-person themselves can unsubscribe"); + throw new AuthorizeException("Only admin or e-person themselves can unsubscribe"); } } @@ -113,33 +107,26 @@ public List getSubscriptionsByEPerson(Context context, EPerson epe } @Override - public List getSubscriptionsByEPersonAndDso(Context context, - EPerson eperson, DSpaceObject dSpaceObject, - Integer limit, Integer offset) - throws SQLException { + public List getSubscriptionsByEPersonAndDso(Context context,EPerson eperson,DSpaceObject dSpaceObject, + Integer limit, Integer offset) throws SQLException { return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, limit, offset); } @Override - public List getAvailableSubscriptions(Context context) - throws SQLException { + public List getAvailableSubscriptions(Context context) throws SQLException { return getAvailableSubscriptions(context, null); } @Override - public List getAvailableSubscriptions(Context context, EPerson eperson) - throws SQLException { - List collections; - if (eperson != null) { + public List getAvailableSubscriptions(Context context, EPerson eperson) throws SQLException { + if (Objects.nonNull(eperson)) { context.setCurrentUser(eperson); } - collections = collectionService.findAuthorized(context, null, Constants.ADD); - return collections; + return collectionService.findAuthorized(context, null, Constants.ADD); } @Override - public boolean isSubscribed(Context context, EPerson eperson, - DSpaceObject dSpaceObject) throws SQLException { + public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException { return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, -1, -1) != null; } @@ -158,7 +145,7 @@ public Subscription findById(Context context, int id) throws SQLException, Autho Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); if (context.getCurrentUser().equals(subscription.getePerson()) || authorizeService.isAdmin(context, context.getCurrentUser())) { - return subscription; + return subscription; } throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); } @@ -187,7 +174,7 @@ public Subscription updateSubscription(Context context, Integer id, @Override public Subscription addSubscriptionParameter(Context context, Integer id, - SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { + SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { // must be admin or the subscriber of the subscription Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); if (authorizeService.isAdmin(context, context.getCurrentUser()) @@ -202,7 +189,7 @@ public Subscription addSubscriptionParameter(Context context, Integer id, @Override public Subscription removeSubscriptionParameter(Context context, Integer id, - SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { + SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { // must be admin or the subscriber of the subscription Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); if (authorizeService.isAdmin(context, context.getCurrentUser()) @@ -228,13 +215,34 @@ public void deleteSubscription(Context context, Integer id) throws SQLException, } catch (SQLException sqlException) { throw new SQLException(sqlException); } - } else { throw new AuthorizeException("Only admin or e-person themselves can delete the subscription"); } } else { - throw new IllegalArgumentException("Subscription with id" + id + "is not found"); + throw new IllegalArgumentException("Subscription with id " + id + " is not found"); } + } + @Override + public List findAllSubscriptionsByTypeAndFrequency(Context context, + String type, String frequencyValue) throws SQLException { + return subscriptionDAO.findAllSubscriptionsByTypeAndFrequency(context, type, frequencyValue); } + + @Override + public Long countAll(Context context) throws SQLException { + return subscriptionDAO.countAll(context); + } + + @Override + public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException { + return subscriptionDAO.countAllByEPerson(context, ePerson); + } + + @Override + public Long countAllByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) + throws SQLException { + return subscriptionDAO.countAllByEPersonAndDso(context, ePerson, dSpaceObject); + } + } diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index 48dd24cdd452..1f5ead43873f 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -26,8 +26,6 @@ import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; - - /** * Database entity representation of the subscription table * @@ -61,9 +59,7 @@ public class Subscription implements ReloadableEntity { * Protected constructor, create object using: * {@link org.dspace.eperson.service.SubscribeService#subscribe(Context, EPerson, DSpaceObject, List, String)} */ - protected Subscription() { - - } + protected Subscription() {} @Override public Integer getID() { diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java index 0b69b365155b..002b517032b2 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java @@ -7,7 +7,6 @@ */ package org.dspace.eperson; - import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -23,25 +22,35 @@ * * @author Alba Aliu at atis.al */ - @Entity @Table(name = "subscription_parameter") -@DiscriminatorColumn(name = "name") public class SubscriptionParameter { + @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "subscription_parameter_seq") - @SequenceGenerator(name = "subscription_parameter_seq", - sequenceName = "subscription_parameter_seq", allocationSize = 1) + @SequenceGenerator(name = "subscription_parameter_seq", sequenceName = "subscription_parameter_seq", + allocationSize = 1) @Column(name = "subscription_parameter_id", unique = true) private Integer id; + @ManyToOne - @JoinColumn(name = "subscription_id") + @JoinColumn(name = "subscription_id", nullable = false) private Subscription subscription; + @Column private String name; + @Column private String value; + public SubscriptionParameter() {} + + public SubscriptionParameter(Integer id, Subscription subscription, String name, String value) { + this.id = id; + this.subscription = subscription; + this.name = name; + this.value = value; + } public Subscription getSubscription() { return subscription; @@ -67,4 +76,12 @@ public void setValue(String value) { this.value = value; } + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index 4f4ed16a2e41..011486622b5a 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -28,11 +28,10 @@ public interface SubscriptionDAO extends GenericDAO { public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException; - public List findByEPerson(Context context, - EPerson eperson, Integer limit, Integer offset) throws SQLException; + public List findByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) + throws SQLException; - public List findByEPersonAndDso(Context context, - EPerson eperson, DSpaceObject dSpaceObject, + public List findByEPersonAndDso(Context context,EPerson eperson, DSpaceObject dSpaceObject, Integer limit, Integer offset) throws SQLException; public void deleteByEPerson(Context context, EPerson eperson) throws SQLException; @@ -41,7 +40,17 @@ public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EP throws SQLException; public List findAllOrderedByIDAndResourceType(Context context, String resourceType, - Integer limit, Integer offset) throws SQLException; + Integer limit, Integer offset) throws SQLException; + + public List findAllOrderedByDSO(Context context, Integer limit, Integer offset) throws SQLException; + + public List findAllSubscriptionsByTypeAndFrequency(Context context, String type,String frequencyValue) + throws SQLException; + + public Long countAll(Context context) throws SQLException; + + public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException; + + public Long countAllByEPersonAndDso(Context context, EPerson ePerson,DSpaceObject dSpaceObject) throws SQLException; - public List findAllOrderedById(Context context, Integer limit, Integer offset) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index 043fd70b57b3..71518c1fcf0a 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -9,10 +9,12 @@ import java.sql.SQLException; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; import javax.persistence.criteria.Root; import org.dspace.content.DSpaceObject; @@ -20,6 +22,8 @@ import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.dspace.eperson.SubscriptionParameter_; import org.dspace.eperson.Subscription_; import org.dspace.eperson.dao.SubscriptionDAO; @@ -31,6 +35,7 @@ * @author kevinvandevelde at atmire.com */ public class SubscriptionDAOImpl extends AbstractHibernateDAO implements SubscriptionDAO { + protected SubscriptionDAOImpl() { super(); } @@ -44,10 +49,9 @@ public List findByEPerson(Context context, EPerson eperson, criteriaQuery.select(subscriptionRoot); criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson)); List orderList = new LinkedList<>(); - orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id))); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, Subscription.class, limit, offset); - } @Override @@ -59,14 +63,12 @@ public List findByEPersonAndDso(Context context, EPerson eperson, getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); - criteriaQuery.where(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.ePerson), eperson)); - criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal( - subscriptionRoot.get(Subscription_.ePerson), eperson), + subscriptionRoot.get(Subscription_.ePerson), eperson), criteriaBuilder.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject) )); List orderList = new LinkedList<>(); - orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id))); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, Subscription.class, limit, offset); } @@ -100,7 +102,7 @@ public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EP @Override public List findAllOrderedByIDAndResourceType(Context context, String resourceType, - Integer limit, Integer offset) throws SQLException { + Integer limit, Integer offset) throws SQLException { String hqlQuery = "select s from Subscription s join %s dso " + "ON dso.id = s.dSpaceObject ORDER BY subscription_id"; if (resourceType != null) { @@ -116,15 +118,69 @@ public List findAllOrderedByIDAndResourceType(Context context, Str query.setHint("org.hibernate.cacheable", false); return query.getResultList(); } + @Override - public List findAllOrderedById(Context context, Integer limit, Integer offset) throws SQLException { + public List findAllOrderedByDSO(Context context, Integer limit, Integer offset) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); List orderList = new LinkedList<>(); - orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id))); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.dSpaceObject))); criteriaQuery.orderBy(orderList); return list(context, criteriaQuery, false, Subscription.class, limit, offset); } + + @Override + public List findAllSubscriptionsByTypeAndFrequency(Context context, + String type, String frequencyValue) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); + Root subscriptionRoot = criteriaQuery.from(Subscription.class); + criteriaQuery.select(subscriptionRoot); + Join childJoin = subscriptionRoot.join("subscriptionParameterList"); + criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.TYPE), type), + criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.name), "frequency"), + criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.value), frequencyValue) + )); + List orderList = new ArrayList<>(1); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.ePerson))); + orderList.add(criteriaBuilder.asc(subscriptionRoot.get(Subscription_.id))); + criteriaQuery.orderBy(orderList); + return list(context, criteriaQuery, false, Subscription.class, 10000, -1); + } + + @Override + public Long countAll(Context context) throws SQLException { + CriteriaBuilder qb = getCriteriaBuilder(context); + CriteriaQuery cq = qb.createQuery(Long.class); + cq.select(qb.count(cq.from(Subscription.class))); + Query query = this.getHibernateSession(context).createQuery(cq); + return (Long) query.getSingleResult(); + } + + @Override + public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException { + CriteriaBuilder qb = getCriteriaBuilder(context); + CriteriaQuery cq = qb.createQuery(Long.class); + Root subscriptionRoot = cq.from(Subscription.class); + cq.select(qb.count(subscriptionRoot)); + cq.where(qb.equal(subscriptionRoot.get(Subscription_.ePerson), ePerson)); + Query query = this.getHibernateSession(context).createQuery(cq); + return (Long) query.getSingleResult(); + } + + @Override + public Long countAllByEPersonAndDso(Context context, + EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException { + CriteriaBuilder qb = getCriteriaBuilder(context); + CriteriaQuery cq = qb.createQuery(Long.class); + Root subscriptionRoot = cq.from(Subscription.class); + cq.select(qb.count(subscriptionRoot)); + cq.where(qb.and(qb.equal(subscriptionRoot.get(Subscription_.ePerson) + , ePerson), qb.equal(subscriptionRoot.get(Subscription_.dSpaceObject), dSpaceObject))); + Query query = this.getHibernateSession(context).createQuery(cq); + return (Long) query.getSingleResult(); + } + } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java index eee80a73a510..37af787ed3a5 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionParameterDAOImpl.java @@ -18,9 +18,11 @@ * * @author Alba Aliu at atis.al */ -public class SubscriptionParameterDAOImpl extends AbstractHibernateDAO - implements SubscriptionParameterDAO { +public class SubscriptionParameterDAOImpl extends AbstractHibernateDAO + implements SubscriptionParameterDAO { + protected SubscriptionParameterDAOImpl() { super(); } + } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 607118ce5619..d06c23f1f0a8 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -33,13 +33,13 @@ public interface SubscribeService { * new item appears in the collection. * * @param context DSpace context - * @param limit Number of subscriptions to return - * @param offset Offset number + * @param limit Number of subscriptions to return + * @param offset Offset number * @return list of Subscription objects * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List findAll(Context context, String resourceType, - Integer limit, Integer offset) throws Exception; + public List findAll(Context context, String resourceType, Integer limit, Integer offset) + throws Exception; /** * Subscribe an e-person to a collection. An e-mail will be sent every day a @@ -67,21 +67,21 @@ public Subscription subscribe(Context context, EPerson eperson, * @throws AuthorizeException Exception indicating the current user of the context does not have permission * to perform a particular action. */ - public void unsubscribe(Context context, EPerson eperson, - DSpaceObject dSpaceObject) throws SQLException, AuthorizeException; + public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject) + throws SQLException, AuthorizeException; /** * Find out which collections an e-person is subscribed to * * @param context DSpace context * @param eperson EPerson - * @param limit Number of subscriptions to return - * @param offset Offset number + * @param limit Number of subscriptions to return + * @param offset Offset number * @return array of collections e-person is subscribed to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getSubscriptionsByEPerson(Context context, EPerson eperson, - Integer limit, Integer offset) throws SQLException; + public List getSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) + throws SQLException; /** * Find out which collections an e-person is subscribed to and related with dso @@ -89,8 +89,8 @@ public List getSubscriptionsByEPerson(Context context, EPerson epe * @param context DSpace context * @param eperson EPerson * @param dSpaceObject DSpaceObject - * @param limit Number of subscriptions to return - * @param offset Offset number + * @param limit Number of subscriptions to return + * @param offset Offset number * @return array of collections e-person is subscribed to and related with dso * @throws SQLException An exception that provides information on a database access error or other errors. */ @@ -107,8 +107,7 @@ public List getSubscriptionsByEPersonAndDso(Context context, * @return array of collections the currently logged in e-person can subscribe to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getAvailableSubscriptions(Context context) - throws SQLException; + public List getAvailableSubscriptions(Context context) throws SQLException; /** * Find out which collections an e-person can subscribe to @@ -118,8 +117,7 @@ public List getAvailableSubscriptions(Context context) * @return array of collections e-person can subscribe to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getAvailableSubscriptions(Context context, EPerson eperson) - throws SQLException; + public List getAvailableSubscriptions(Context context, EPerson eperson) throws SQLException; /** * Is that e-person subscribed to that collection? @@ -130,8 +128,7 @@ public List getAvailableSubscriptions(Context context, EPerson epers * @return true if they are subscribed * @throws SQLException An exception that provides information on a database access error or other errors. */ - public boolean isSubscribed(Context context, EPerson eperson, - DSpaceObject dSpaceObject) throws SQLException; + public boolean isSubscribed(Context context, EPerson eperson, DSpaceObject dSpaceObject) throws SQLException; /** * Delete subscription by collection. @@ -155,7 +152,7 @@ public boolean isSubscribed(Context context, EPerson eperson, * Finds a subscription by id * * @param context DSpace context - * @param id the id of subscription to be searched + * @param id the id of subscription to be searched * @throws SQLException An exception that provides information on a database access error or other errors. */ public Subscription findById(Context context, int id) throws SQLException, AuthorizeException; @@ -184,8 +181,7 @@ public Subscription updateSubscription(Context context, Integer id, EPerson eper * @param subscriptionParameter SubscriptionParameter subscriptionParameter * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription addSubscriptionParameter(Context context, Integer id, - SubscriptionParameter subscriptionParameter) + public Subscription addSubscriptionParameter(Context context,Integer id,SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; /** @@ -197,13 +193,51 @@ public Subscription addSubscriptionParameter(Context context, Integer id, * @throws SQLException An exception that provides information on a database access error or other errors. */ public Subscription removeSubscriptionParameter(Context context, Integer id, - SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; + SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; /** * Deletes a subscription * * @param context DSpace context + * @param id Integer id of subscription * @throws SQLException An exception that provides information on a database access error or other errors. */ public void deleteSubscription(Context context, Integer id) throws SQLException, AuthorizeException; -} + + /** + * Finds all subscriptions having given type and frequency + * + * @param context DSpace context + * @param type String type of subscription + * @param frequencyValue String frequency value of subscription + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public List findAllSubscriptionsByTypeAndFrequency(Context context, String type,String frequencyValue) + throws SQLException; + + /** + * Counts all subscriptions + * + * @param context DSpace context + */ + public Long countAll(Context context) throws SQLException, AuthorizeException; + + /** + * Counts all subscriptions by ePerson + * + * @param context DSpace context + * @param ePerson EPerson ePerson + */ + public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException, AuthorizeException; + + /** + * Counts all subscriptions by ePerson and DSO + * + * @param context DSpace context + * @param ePerson EPerson ePerson + * @param dSpaceObject DSpaceObject dSpaceObject + */ + public Long countAllByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) + throws SQLException, AuthorizeException; + +} \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index e94ec3c5049f..ab20e3525730 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -20,7 +20,7 @@ CREATE TABLE subscription_parameter ( subscription_parameter_id INTEGER NOT NULL, name CHARACTER VARYING(255), - value CHARACTER VARYING(255), + `value` CHARACTER VARYING(255), subscription_id INTEGER NOT NULL, CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java index 3306ced8f434..13122fe4a771 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -42,6 +42,7 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; +import org.dspace.eperson.service.SubscribeService; import org.dspace.orcid.factory.OrcidServiceFactory; import org.dspace.orcid.service.OrcidHistoryService; import org.dspace.orcid.service.OrcidQueueService; @@ -102,6 +103,7 @@ public abstract class AbstractBuilder { static OrcidHistoryService orcidHistoryService; static OrcidQueueService orcidQueueService; static OrcidTokenService orcidTokenService; + static SubscribeService subscribeService; protected Context context; @@ -161,6 +163,7 @@ public static void init() { orcidHistoryService = OrcidServiceFactory.getInstance().getOrcidHistoryService(); orcidQueueService = OrcidServiceFactory.getInstance().getOrcidQueueService(); orcidTokenService = OrcidServiceFactory.getInstance().getOrcidTokenService(); + subscribeService = ContentServiceFactory.getInstance().getSubscribeService(); } @@ -194,7 +197,7 @@ public static void destroy() { requestItemService = null; versioningService = null; orcidTokenService = null; - + subscribeService = null; } public static void cleanupObjects() throws Exception { diff --git a/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java index 1d714b856708..9c1c778d56f2 100644 --- a/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java @@ -7,6 +7,9 @@ */ package org.dspace.builder; +import java.sql.SQLException; +import java.util.List; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; @@ -18,9 +21,6 @@ import org.dspace.eperson.SubscriptionParameter; import org.dspace.eperson.service.SubscribeService; -import java.sql.SQLException; -import java.util.List; - public class SubscribeBuilder extends AbstractBuilder { /* Log4j logger*/ @@ -80,16 +80,17 @@ public void delete(Subscription subscription) throws Exception { } c.complete(); } - indexingService.commit(); } - public static SubscribeBuilder subscribeBuilder(final Context context, String type, DSpaceObject dSpaceObject, EPerson ePerson, List subscriptionParameterList) { + public static SubscribeBuilder subscribeBuilder(final Context context, String type, DSpaceObject dSpaceObject, + EPerson ePerson, List subscriptionParameterList) { SubscribeBuilder builder = new SubscribeBuilder(context); return builder.create(context, type, dSpaceObject, ePerson, subscriptionParameterList); } - private SubscribeBuilder create(Context context, String type, DSpaceObject dSpaceObject, EPerson ePerson, List subscriptionParameterList) { + private SubscribeBuilder create(Context context, String type, DSpaceObject dSpaceObject, EPerson ePerson, + List subscriptionParameterList) { try { this.context = context; @@ -101,4 +102,5 @@ private SubscribeBuilder create(Context context, String type, DSpaceObject dSpac } return this; } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java index 7e7ce6ee495b..bc89116c261e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java @@ -15,6 +15,7 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.Utils; import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -26,16 +27,24 @@ */ @Component public class SubscriptionConverter implements DSpaceConverter { + @Autowired protected Utils utils; - @Autowired - private ConverterService converter; @Override public SubscriptionRest convert(Subscription subscription, Projection projection) { SubscriptionRest rest = new SubscriptionRest(); + rest.setProjection(projection); rest.setId(subscription.getID()); - rest.setSubscriptionParameterList(subscription.getSubscriptionParameterList()); + List subscriptionParameterRestList = new ArrayList<>(); + for (SubscriptionParameter subscriptionParameter : subscription.getSubscriptionParameterList()) { + SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); + subscriptionParameterRest.setName(subscriptionParameter.getName()); + subscriptionParameterRest.setValue(subscriptionParameter.getValue()); + subscriptionParameterRest.setId(subscriptionParameter.getId()); + subscriptionParameterRestList.add(subscriptionParameterRest); + } + rest.setSubscriptionParameterList(subscriptionParameterRestList); rest.setType(subscription.getType()); return rest; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index d158d3ba91a8..394805a728a2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -13,12 +13,15 @@ import org.dspace.app.rest.RestResourceController; -@LinksRest(links = {@LinkRest(name = SubscriptionRest.DSPACE_OBJECT, +@LinksRest(links = { @LinkRest(name = SubscriptionRest.DSPACE_OBJECT, method = "getDSpaceObject"), @LinkRest( name = SubscriptionRest.EPERSON, method = "getEPerson") }) public class SubscriptionRest extends BaseObjectRest { + + private static final long serialVersionUID = 1L; + public static final String NAME = "subscription"; public static final String NAME_PLURAL = "subscriptions"; public static final String CATEGORY = "core"; @@ -27,7 +30,7 @@ public class SubscriptionRest extends BaseObjectRest { private Integer id; private String type; - private List subscriptionParameterList = new ArrayList<>(); + private List subscriptionParameterList = new ArrayList<>(); @Override public String getCategory() { @@ -35,7 +38,7 @@ public String getCategory() { } @Override - public Class getController() { + public Class getController() { return RestResourceController.class; } @@ -48,11 +51,11 @@ public void setType(String type) { this.type = type; } - public List getSubscriptionParameterList() { + public List getSubscriptionParameterList() { return subscriptionParameterList; } - public void setSubscriptionParameterList(List subscriptionParameterList) { + public void setSubscriptionParameterList(List subscriptionParameterList) { this.subscriptionParameterList = subscriptionParameterList; } @@ -69,4 +72,5 @@ public Integer getId() { public void setId(Integer id) { this.id = id; } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index ffd2d606dd3b..77215cdd50f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.Objects; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; @@ -15,7 +16,9 @@ import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; -import org.dspace.core.Context; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.SubscribeService; import org.hibernate.proxy.HibernateProxy; @@ -29,31 +32,33 @@ * Link repository for "dataSpaceObject" of subscription */ @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.DSPACE_OBJECT) -@Transactional public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired - SubscribeService subscribeService; - + private SubscribeService subscribeService; public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, - Integer subscriptionId, @Nullable Pageable optionalPageable, - Projection projection) throws AuthorizeException { + Integer subscriptionId, Projection projection) throws AuthorizeException { try { - Context context = obtainContext(); - Subscription subscription = subscribeService.findById(context, subscriptionId); - if (subscription == null) { + Subscription subscription = subscribeService.findById(obtainContext(), subscriptionId); + if (Objects.isNull(subscription)) { throw new ResourceNotFoundException("No such subscription: " + subscriptionId); } - HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); - LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); - - return converter.toRest(initializer.getImplementation(), projection); + if (subscription.getdSpaceObject() instanceof Item || + subscription.getdSpaceObject() instanceof Community || + subscription.getdSpaceObject() instanceof Collection) { + return converter.toRest(subscription.getdSpaceObject(), projection); + } else { + HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); + LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); + return converter.toRest(initializer.getImplementation(), projection); + } } catch (SQLException e) { throw new RuntimeException(e); } catch (AuthorizeException e) { throw new AuthorizeException(e.getMessage()); } } -} + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index b1cbc5713581..4a7181f0da2d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.Objects; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; @@ -15,7 +16,6 @@ import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; -import org.dspace.core.Context; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.SubscribeService; import org.springframework.beans.factory.annotation.Autowired; @@ -30,25 +30,22 @@ public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { @Autowired - SubscribeService subscribeService; + private SubscribeService subscribeService; - public EPersonRest getEPerson(@Nullable HttpServletRequest request, - Integer subscriptionId, + public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subscriptionId, @Nullable Pageable optionalPageable, Projection projection) throws AuthorizeException { try { - Context context = obtainContext(); - Subscription subscription = subscribeService.findById(context, subscriptionId); - if (subscription == null) { + Subscription subscription = subscribeService.findById(obtainContext(), subscriptionId); + if (Objects.isNull(subscription)) { throw new ResourceNotFoundException("No such subscription: " + subscriptionId); } - return converter.toRest(subscription.getePerson(), projection); } catch (SQLException e) { throw new RuntimeException(e); - } - catch (AuthorizeException e) { + } catch (AuthorizeException e) { throw new AuthorizeException(e.getMessage()); } } -} + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 6c2e49c02266..e40caff8cd03 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -7,22 +7,23 @@ */ package org.dspace.app.rest.repository; - import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.UUID; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.BadRequestException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.ResourcePatch; @@ -30,12 +31,13 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.SubscribeService; -import org.dspace.eperson.service.SubscriptionParameterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -43,41 +45,34 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.sql.SQLException; -import java.util.List; - /** * This is the repository responsible to manage SubscriptionRest object * * @author Alba Aliu at atis.al */ - @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME) -public class SubscriptionRestRepository extends DSpaceRestRepository - implements LinkRestRepository { - private static final Logger log = LogManager.getLogger(); +public class SubscriptionRestRepository extends DSpaceRestRepository + implements LinkRestRepository { + @Autowired - AuthorizeService authorizeService; + private ConverterService converter; @Autowired - SubscribeService subscribeService; + private EPersonService personService; @Autowired - protected ConverterService converter; + private AuthorizeService authorizeService; @Autowired - protected EPersonService personService; + private SubscribeService subscribeService; @Autowired private DSpaceObjectUtils dspaceObjectUtil; @Autowired private ResourcePatch resourcePatch; - @Override @PreAuthorize("isAuthenticated()") public SubscriptionRest findOne(Context context, Integer id) { try { Subscription subscription = subscribeService.findById(context, id); - if (subscription == null) { + if (Objects.isNull(subscription)) { throw new ResourceNotFoundException("The subscription for ID: " + id + " could not be found"); } return converter.toRest(subscription, utils.obtainProjection()); @@ -96,14 +91,16 @@ public Page findAll(Context context, Pageable pageable) { String resourceType = req.getParameter("resourceType"); List subscriptionList = subscribeService.findAll(context, resourceType, pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); - return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); + Long total = subscribeService.countAll(context); + return converter.toRestPage(subscriptionList, pageable, total, utils.obtainProjection()); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } + @PreAuthorize("isAuthenticated()") @SearchRestMethod(name = "findByEPerson") - public Page findAllByEPerson(Context context, String id) { + public Page findAllSubscriptionsByEPerson(String id, Pageable pageable) throws Exception { try { Context context = obtainContext(); EPerson ePerson = personService.findByIdOrLegacyId(context, id); @@ -111,14 +108,19 @@ public Page findAllByEPerson(Context context, String id) { || authorizeService.isAdmin(context, context.getCurrentUser())) { List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, ePerson, pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); - return converter.toRestPage(subscriptionList, pageable, utils.obtainProjection()); + Long total = subscribeService.countAllByEPerson(context, ePerson); + return converter.toRestPage(subscriptionList, pageable, total, utils.obtainProjection()); } else { throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); } - } catch (SQLException | AuthorizeException sqlException) { - throw new RuntimeException(sqlException.getMessage(), sqlException); + } catch (SQLException sqlException) { + throw new SQLException(sqlException.getMessage(), sqlException); + + } catch (AuthorizeException authorizeException) { + throw new AuthorizeException(authorizeException.getMessage()); } } + @PreAuthorize("isAuthenticated()") @SearchRestMethod(name = "findByEPersonAndDso") public Page findByEPersonAndDso(Pageable pageable) throws Exception { @@ -138,7 +140,8 @@ public Page findByEPersonAndDso(Pageable pageable) throws Exce List subscriptionList = subscribeService.getSubscriptionsByEPersonAndDso(context, ePerson, dSpaceObject, pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); - return converter.toRestPage(subscriptionList, pageable, subscriptionList.size(), + Long total = subscribeService.countAllByEPersonAndDSO(context, ePerson, dSpaceObject); + return converter.toRestPage(subscriptionList, pageable, total, utils.obtainProjection()); } else { throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); @@ -150,22 +153,38 @@ public Page findByEPersonAndDso(Pageable pageable) throws Exce throw new AuthorizeException(authorizeException.getMessage()); } } + @Override @PreAuthorize("isAuthenticated()") - protected SubscriptionRest createAndReturn(Context context) { + protected SubscriptionRest createAndReturn(Context context) throws SQLException, AuthorizeException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); - ObjectMapper mapper = new ObjectMapper(); - SubscriptionRest subscriptionRest = null; - DSpaceObject dSpaceObject = null; - try { - subscriptionRest = mapper.readValue(req.getInputStream(), SubscriptionRest.class); - } catch (IOException e1) { + String epersonId = req.getParameter("eperson_id"); + String dsoId = req.getParameter("dspace_object_id"); + // dso must always be set + if (dsoId == null || epersonId == null) { throw new UnprocessableEntityException("error parsing the body"); } + ObjectMapper mapper = new ObjectMapper(); try { - Subscription subscription = null; DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); EPerson ePerson = personService.findByIdOrLegacyId(context, epersonId); + if (ePerson == null || dSpaceObject == null) { + throw new BadRequestException("Id of person or dspace object must represents reals ids"); + } + // user must have read permissions to dataspace object + if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, Constants.READ, true)) { + throw new AuthorizeException("The user has not READ rights on this DSO"); + } + // if user is admin do not make this control, + // otherwise make this control because normal user can only subscribe with their own ID of user. + if (!authorizeService.isAdmin(context)) { + if (!ePerson.equals(context.getCurrentUser())) { + throw new AuthorizeException("Only administrator can subscribe for other persons"); + } + } + ServletInputStream input = req.getInputStream(); + SubscriptionRest subscriptionRest = mapper.readValue(input, SubscriptionRest.class); + Subscription subscription = null; List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); if (subscriptionParameterList != null) { List subscriptionParameters = new ArrayList<>(); @@ -178,12 +197,17 @@ protected SubscriptionRest createAndReturn(Context context) { subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, - subscriptionRest.getType()); + subscriptionRest.getSubscriptionType()); } context.commit(); return converter.toRest(subscription, utils.obtainProjection()); - } catch (SQLException | AuthorizeException e) { - throw new RuntimeException(e.getMessage(), e); + } catch (SQLException sqlException) { + throw new SQLException(sqlException.getMessage(), sqlException); + + } catch (AuthorizeException authorizeException) { + throw new AuthorizeException(authorizeException.getMessage()); + } catch (IOException ioException) { + throw new UnprocessableEntityException("error parsing the body"); } } @@ -191,20 +215,30 @@ protected SubscriptionRest createAndReturn(Context context) { @PreAuthorize("isAuthenticated()") protected SubscriptionRest put(Context context, HttpServletRequest request, String apiCategory, String model, Integer id, JsonNode jsonNode) throws SQLException, AuthorizeException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + String epersonId = req.getParameter("eperson_id"); + String dsoId = req.getParameter("dspace_object_id"); SubscriptionRest subscriptionRest = null; + DSpaceObject dSpaceObject = null; + EPerson ePerson = null; try { subscriptionRest = new ObjectMapper().readValue(jsonNode.toString(), SubscriptionRest.class); } catch (IOException e) { throw new UnprocessableEntityException("Error parsing subscription json: " + e.getMessage()); } - Subscription subscription = null; String notFoundException = "ResourceNotFoundException:" + apiCategory + "." + model + " with id: " + id + " not found"; + Subscription subscription; try { subscription = subscribeService.findById(context, id); if (subscription == null) { throw new ResourceNotFoundException(notFoundException); } + dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); + ePerson = personService.findByIdOrLegacyId(context, epersonId); + if (dSpaceObject == null || ePerson == null) { + throw new ResourceNotFoundException(notFoundException); + } } catch (SQLException e) { throw new ResourceNotFoundException(notFoundException); } catch (AuthorizeException e) { @@ -227,20 +261,21 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri } else { throw new IllegalArgumentException("The id in the Json and the id in the url do not match: " + id + ", " - + subscriptionRest.getId()); + + subscription.getID()); } } @Override @PreAuthorize("isAuthenticated()") - public void patch(Context context, HttpServletRequest request, String apiCategory, - String model, Integer id, Patch patch) + public void patch(Context context,HttpServletRequest request,String category, String model, Integer id, Patch patch) throws UnprocessableEntityException, DSpaceBadRequestException, AuthorizeException { - Subscription subscription = null; try { - subscription = subscribeService.findById(context, id); + Subscription subscription = subscribeService.findById(context, id); if (subscription == null) { - throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); + throw new ResourceNotFoundException(category + "." + model + " with id: " + id + " not found"); + } + if (!authorizeService.isAdmin(context) || subscription.getePerson().equals(context.getCurrentUser())) { + throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); } resourcePatch.patch(context, subscription, patch.getOperations()); } catch (SQLException e) { @@ -254,11 +289,13 @@ public void patch(Context context, HttpServletRequest request, String apiCategor @Override @PreAuthorize("isAuthenticated()") - public void delete(Context context, Integer id) { + public void delete(Context context, Integer id) throws AuthorizeException { try { subscribeService.deleteSubscription(context, id); - } catch (SQLException | AuthorizeException sqlException) { + } catch (SQLException sqlException) { throw new RuntimeException(sqlException.getMessage(), sqlException); + } catch (AuthorizeException authorizeException) { + throw new AuthorizeException(authorizeException.getMessage()); } } @@ -267,4 +304,4 @@ public Class getDomainClass() { return SubscriptionRest.class; } -} +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java index b0f7c9a31c50..c9149aa115bf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java @@ -16,10 +16,8 @@ import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.patch.JsonValueEvaluator; import org.dspace.app.rest.model.patch.Operation; -import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.Subscription; -import org.dspace.eperson.SubscriptionParameter; import org.dspace.eperson.service.SubscriptionParameterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -38,7 +36,6 @@ public class SubscriptionParameterReplaceOperation extends PatchOperation { return subscriptionParameter.getId().equals(id); }).findFirst().orElseThrow(); - } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index 1422ec55a251..dc3519fe0763 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -7,6 +7,21 @@ */ package org.dspace.app.rest; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.SubscriptionRest; @@ -19,11 +34,9 @@ import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SubscribeBuilder; import org.dspace.content.Collection; -import org.dspace.content.Community; import org.dspace.content.Item; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.SiteService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; @@ -31,71 +44,60 @@ import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.is; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - /** * Integration test to test the /api/config/subscriptions endpoint * (Class has to start or end with IT to be picked up by the failsafe plugin) + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationTest { - private Collection collection; + private Item publicItem; + private Collection collection; - @Override @Before + @Override public void setUp() throws Exception { super.setUp(); context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community community = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - collection = CollectionBuilder.createCollection(context, community).withName("Collection 1").build(); + .withName("Parent Community") + .build(); + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); // creation of the item which will be the DSO related with a subscription publicItem = ItemBuilder.createItem(context, collection) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + context.restoreAuthSystemState(); } - // FIND ALL @Test public void findAll() throws Exception { context.turnOffAuthorisationSystem(); - String token = getAuthToken(admin.getEmail(), password); + List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TypeTest", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TypeTest", publicItem, admin, subscriptionParameterList).build(); subscriptionParameter.setSubscription(subscription); - //When we call the root endpoint + context.restoreAuthSystemState(); - getClient(token).perform(get("/api/core/subscriptions")) - //The status has to be 200 OK + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions")) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) @@ -105,9 +107,12 @@ public void findAll() throws Exception { .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", + Matchers.endsWith("dSpaceObject"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) @@ -116,42 +121,34 @@ public void findAll() throws Exception { @Test public void findAllAnonymous() throws Exception { - //When we call the root endpoint as anonymous user getClient().perform(get("/api/core/subscriptions")) - //The status has to be 401 Not Authorized - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()); } @Test public void findAllAsUser() throws Exception { - context.turnOffAuthorisationSystem(); - String token = getAuthToken(eperson.getEmail(), password); - context.restoreAuthSystemState(); - //When we call the root endpoint as simple user - getClient(token).perform(get("/api/core/subscriptions")) - //The status has to be 403 Forbidden - .andExpect(status().isForbidden()); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions")) + .andExpect(status().isForbidden()); } @Test public void findAllWithResourceType() throws Exception { context.turnOffAuthorisationSystem(); - //When we call the root endpoint as anonymous user - getClient().perform(get("/api/core/subscriptions")) - //The status has to be 401 Not Authorized - .andExpect(status().isUnauthorized()); - String token = getAuthToken(admin.getEmail(), password); + List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TypeTest", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TypeTest", publicItem, admin, subscriptionParameterList).build(); subscriptionParameter.setSubscription(subscription); - //When we call the root endpoint + context.restoreAuthSystemState(); - getClient(token).perform(get("/api/core/subscriptions?resourceType=Item")) - //The status has to be 200 OK + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions?resourceType=Item")) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) @@ -161,16 +158,20 @@ public void findAllWithResourceType() throws Exception { .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", + Matchers.endsWith("dSpaceObject"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) - .andExpect(jsonPath("$._links.self.href", Matchers.is(REST_SERVER_URL + "core/subscriptions?resourceType=Item"))); + .andExpect(jsonPath("$._links.self.href", + Matchers.is(REST_SERVER_URL + "core/subscriptions?resourceType=Item"))); + // search for subscriptions related with collections - getClient(token).perform(get("/api/core/subscriptions?resourceType=Collection")) - //The status has to be 200 OK + getClient(tokenAdmin).perform(get("/api/core/subscriptions?resourceType=Collection")) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) @@ -179,24 +180,24 @@ public void findAllWithResourceType() throws Exception { .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(0))) .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(0))) .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._links.self.href", Matchers.is(REST_SERVER_URL + "core/subscriptions?resourceType=Collection"))); + .andExpect(jsonPath("$._links.self.href", + Matchers.is(REST_SERVER_URL + "core/subscriptions?resourceType=Collection"))); } - // FIND BY ID @Test public void findByIdAsAdministrator() throws Exception { context.turnOffAuthorisationSystem(); - String token = getAuthToken(admin.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter"); subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); - //When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) - //The status has to be 200 OK + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID())) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) @@ -204,13 +205,14 @@ public void findByIdAsAdministrator() throws Exception { .andExpect(jsonPath("$.subscriptionType", is("TestType"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) + .andExpect(jsonPath("$._links.dSpaceObject.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); - - } @Test @@ -221,75 +223,73 @@ public void findByIdAsAnonymous() throws Exception { subscriptionParameter.setName("Parameter"); subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); - //When we call the root endpoint + getClient().perform(get("/api/core/subscriptions/" + subscription.getID())) - //The status has to be 401 - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()); } @Test + //TODO public void findByIdNotAsSubscriberNotAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); - String token = getAuthToken(eperson.getEmail(), password); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter"); subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); - //When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/" + subscription.getID())) - //The status has to be 500 + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID())) .andExpect(status().isInternalServerError()); } - // FIND ALL BY EPERSON/DSO @Test public void findAllSubscriptionsByEPerson() throws Exception { - //When we call the root endpoint as anonymous user -// getClient().perform(get("/api/core/subscriptions")) -// //The status has to be 403 Not Authorized -// .andExpect(status().isUnauthorized()); - String token = getAuthToken(eperson.getEmail(), password); context.turnOffAuthorisationSystem(); EPerson user = EPersonBuilder.createEPerson(context) - .withEmail("user@test.it") - .withPassword(password) - .build(); + .withEmail("user@test.it") + .withPassword(password) + .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter1"); subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, user, subscriptionParameterList).build(); + SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, user, subscriptionParameterList).build(); context.restoreAuthSystemState(); - //When we call the root endpoint - getClient(token).perform(get("/api/core/subscriptions/search/findByEPerson?id=" + eperson.getID())) - //The status has to be 200 OK + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/search/findByEPerson?id=" + eperson.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TestType"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", + Matchers.endsWith("dSpaceObject"))) + .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Parameter1"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("ValueParameter1"))); + .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", + is("ValueParameter1"))); + context.turnOffAuthorisationSystem(); + EPersonBuilder.createEPerson(context) + .withEmail("epersonIT@example.com") + .withPassword(password) + .withLanguage("al") + .build(); - EPerson epersonIT = EPersonBuilder.createEPerson(context) - .withEmail("epersonIT@example.com") - .withPassword(password) - .withLanguage("al") - .build(); - List subscriptionParameterList = new ArrayList<>(); - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Parameter1"); subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); @@ -298,19 +298,20 @@ public void findAllSubscriptionsByEPerson() throws Exception { subscriptionParameter1.setName("Parameter1"); subscriptionParameter1.setValue("ValueParameter1"); subscriptionParameterList1.add(subscriptionParameter1); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", collection, user, subscriptionParameterList).build(); - Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, "Test", collection, user, subscriptionParameterList1).build(); + SubscribeBuilder.subscribeBuilder(context, "TestType", collection, user, subscriptionParameterList).build(); + SubscribeBuilder.subscribeBuilder(context, "Test", collection, user, subscriptionParameterList1).build(); context.restoreAuthSystemState(); - //When we call the root endpoint - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/core/subscriptions/search/findByEPersonAndDso?dspace_object_id=" + collection.getID() + "&eperson_id=" + user.getID())) - //The status has to be 200 OK - .andExpect(status().isUnauthorized()); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("dspace_object_id", collection.getID().toString()) + .param("eperson_id", user.getID().toString())) + .andExpect(status().isUnauthorized()); } - // ADD @Test public void addSubscriptionNotLoggedIn() throws Exception { + context.turnOffAuthorisationSystem(); SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); subscriptionParameterRest.setValue("nameTest"); subscriptionParameterRest.setName("valueTest"); @@ -321,17 +322,19 @@ public void addSubscriptionNotLoggedIn() throws Exception { MultiValueMap params = new LinkedMultiValueMap(); params.add("dspace_object_id", publicItem.getID().toString()); params.add("eperson_id", eperson.getID().toString()); + context.restoreAuthSystemState(); + ObjectMapper objectMapper = new ObjectMapper(); - getClient().perform(post("/api/core/subscriptions?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + eperson.getID()) - .content(objectMapper.writeValueAsString(subscriptionRest)) - .contentType(contentType)) - //The status has to be 401 Not Authorized - .andExpect(status().isUnauthorized()); + getClient().perform(post("/api/core/subscriptions") + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(objectMapper.writeValueAsString(subscriptionRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); } @Test public void addSubscriptionAsAdmin() throws Exception { - //When we call the root endpoint as anonymous user SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); subscriptionParameterRest.setValue("nameTest"); subscriptionParameterRest.setName("valueTest"); @@ -339,9 +342,7 @@ public void addSubscriptionAsAdmin() throws Exception { subscriptionParameterRestList.add(subscriptionParameterRest); SubscriptionRest subscriptionRest = new SubscriptionRest(); subscriptionRest.setType("testType"); -// subscriptionRest.setSubscriptionParameterList(subscriptionParameterRestList); ObjectMapper objectMapper = new ObjectMapper(); - String token = getAuthToken(admin.getEmail(), password); Map map = new HashMap<>(); map.put("type", "test"); List> list = new ArrayList<>(); @@ -350,10 +351,13 @@ public void addSubscriptionAsAdmin() throws Exception { sub_list.put("value", "daily"); list.add(sub_list); map.put("subscriptionParameterList", list); - getClient(token).perform(post("/api/core/subscriptions?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + admin.getID()) - .content(objectMapper.writeValueAsString(map)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - //The status has to be 200 OK + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", admin.getID().toString()) + .content(objectMapper.writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" //By default we expect at least 1 submission forms so this to be reflected in the page object @@ -362,13 +366,14 @@ public void addSubscriptionAsAdmin() throws Exception { .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("nameTest"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("valueTest"))) .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("ePerson"))); } - // PUT @Test public void editSubscriptionAnonymous() throws Exception { context.turnOffAuthorisationSystem(); @@ -377,9 +382,9 @@ public void editSubscriptionAnonymous() throws Exception { subscriptionParameter.setName("Parameter1"); subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, admin, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, admin, subscriptionParameterList).build(); ObjectMapper objectMapper = new ObjectMapper(); - String token = getAuthToken(admin.getEmail(), password); Map newSubscription = new HashMap<>(); newSubscription.put("type", "test"); List> list = new ArrayList<>(); @@ -389,15 +394,17 @@ public void editSubscriptionAnonymous() throws Exception { list.add(sub_list); newSubscription.put("subscriptionParameterList", list); context.restoreAuthSystemState(); - //When we call the root endpoint as anonymous user - getClient().perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + admin.getID()) - .content(objectMapper.writeValueAsString(newSubscription)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - //The status has to be 403 Not Authorized - .andExpect(status().isUnauthorized()); + + getClient().perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", admin.getID().toString()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnauthorized()); } @Test + //TODO public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); EPerson epersonIT = EPersonBuilder.createEPerson(context) @@ -410,10 +417,10 @@ public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { subscriptionParameter.setName("Parameter1"); subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, eperson, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); - String token = getAuthToken(epersonIT.getEmail(), password); Map newSubscription = new HashMap<>(); newSubscription.put("type", "test"); List> list = new ArrayList<>(); @@ -422,8 +429,11 @@ public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { sub_list.put("value", "daily"); list.add(sub_list); newSubscription.put("subscriptionParameterList", list); - //When we call the root endpoint as anonymous user - getClient(token).perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + admin.getID()) + + String token = getAuthToken(epersonIT.getEmail(), password); + getClient(token).perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", admin.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) //The status has to be 500 Error @@ -432,15 +442,14 @@ public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { @Test public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { - String tokenAdmin = getAuthToken(admin.getEmail(), password); - String tokenSubscriber = getAuthToken(eperson.getEmail(), password); context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, eperson, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); @@ -451,11 +460,13 @@ public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { sub_list.put("value", "daily"); list.add(sub_list); newSubscription.put("subscriptionParameterList", list); + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); - getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID() + "?dspace_object_id=" + publicItem.getID() + "&eperson_id=" + eperson.getID()) - //The status has to be 403 Not Authorized - .content(objectMapper.writeValueAsString(newSubscription)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) @@ -464,32 +475,33 @@ public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); } - // DELETE @Test public void deleteSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); EPerson epersonIT = EPersonBuilder.createEPerson(context) - .withEmail("epersonIT@example.com") - .withPassword(password) - .withLanguage("al") - .build(); - String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); + .withEmail("epersonIT@example.com") + .withPassword(password) + .withLanguage("al") + .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); + SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); + + String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); getClient(epersonITtoken).perform(put("/api/core/subscriptions")) - //The status has to be 403 Not Authorized - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()); } @Test @@ -500,16 +512,14 @@ public void deleteSubscriptionAsAdmin() throws Exception { subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); - String token = getAuthToken(admin.getEmail(), password); + SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(put("/api/core/subscriptions")) - //The status has to be 403 Not Authorized - .andExpect(status().isOk()); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(put("/api/core/subscriptions")) + .andExpect(status().isOk()); } - // PATCH @Test public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { context.turnOffAuthorisationSystem(); @@ -518,19 +528,21 @@ public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { subscriptionParameter.setName("TestName"); subscriptionParameter.setValue("TestValue"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); - String token = getAuthToken(admin.getEmail(), password); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "Test", publicItem, eperson, subscriptionParameterList).build(); List ops = new ArrayList(); Map value = new HashMap<>(); value.put("name", "frequency"); value.put("value", "monthly"); - ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId(), value); + ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/" + + subscription.getSubscriptionParameterList().get(0).getId(), value); ops.add(replaceOperation); String patchBody = getPatchContent(ops); - getClient(token).perform(patch("/api/core/subscriptions/" + subscription.getID()) - .content(patchBody) - ) - //The status has to be 403 Not Authorized + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(patch("/api/core/subscriptions/" + subscription.getID()) + .content(patchBody)) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) @@ -539,10 +551,14 @@ public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("monthly"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", + Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", + Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", + Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); } @@ -554,12 +570,14 @@ public void patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Excepti subscriptionParameter.setName("TestName"); subscriptionParameter.setValue("TestValue"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "Test", publicItem, eperson, subscriptionParameterList).build(); List ops = new ArrayList(); Map value = new HashMap<>(); value.put("name", "frequency"); value.put("value", "monthly"); - ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId(), value); + ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/" + + subscription.getSubscriptionParameterList().get(0).getId(), value); ops.add(replaceOperation); String patchBody = getPatchContent(ops); EPerson epersonIT = EPersonBuilder.createEPerson(context) @@ -567,12 +585,12 @@ public void patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Excepti .withPassword(password) .withLanguage("al") .build(); + context.restoreAuthSystemState(); + String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); getClient(epersonITtoken).perform(patch("/api/core/subscriptions/" + subscription.getID()) - .content(patchBody) - ) - //The status has to be 403 Forbidden - .andExpect(status().isForbidden()); + .content(patchBody)) + .andExpect(status().isForbidden()); } @Test @@ -583,19 +601,19 @@ public void patchAddSubscriptionParameter() throws Exception { subscriptionParameter.setName("TestName"); subscriptionParameter.setValue("TestValue"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "Test", publicItem, eperson, subscriptionParameterList).build(); String token = getAuthToken(admin.getEmail(), password); List ops = new ArrayList(); Map value = new HashMap<>(); value.put("name", "frequency"); value.put("value", "monthly"); - AddOperation addOperation = new AddOperation("/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId(), value); + AddOperation addOperation = new AddOperation("/subscriptionsParameter/" + + subscription.getSubscriptionParameterList().get(0).getId(), value); ops.add(addOperation); String patchBody = getPatchContent(ops); getClient(token).perform(patch("/api/core/subscriptions/" + subscription.getID()) - .content(patchBody) - ) - //The status has to be 403 Not Authorized + .content(patchBody)) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) @@ -606,10 +624,14 @@ public void patchAddSubscriptionParameter() throws Exception { .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("TestValue"))) .andExpect(jsonPath("$.subscriptionParameterList[1].name", is("frequency"))) .andExpect(jsonPath("$.subscriptionParameterList[1].value", is("monthly"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", + Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", + Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) + .andExpect(jsonPath("$._links.ePerson.href", + Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); } @@ -621,20 +643,21 @@ public void patchRemoveSubscriptionParameter() throws Exception { subscriptionParameter.setName("TestName"); subscriptionParameter.setValue("TestValue"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); - String token = getAuthToken(admin.getEmail(), password); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "Test", publicItem, eperson, subscriptionParameterList).build(); List ops = new ArrayList(); Map value = new HashMap<>(); value.put("name", "frequency"); value.put("value", "monthly"); - RemoveOperation removeOperation = new RemoveOperation("/subscriptionsParameter/" + subscription.getSubscriptionParameterList().get(0).getId()); + RemoveOperation removeOperation = new RemoveOperation("/subscriptionsParameter/" + + subscription.getSubscriptionParameterList().get(0).getId()); ops.add(removeOperation); String patchBody = getPatchContent(ops); context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); getClient(token).perform(patch("/api/core/subscriptions/" + subscription.getID()) - .content(patchBody) - ) - //The status has to be 403 Not Authorized + .content(patchBody)) .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) @@ -642,6 +665,8 @@ public void patchRemoveSubscriptionParameter() throws Exception { .andExpect(jsonPath("$.type", is("Test"))) .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) .andExpect(jsonPath("$.subscriptionParameterList", Matchers.arrayWithSize(0))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))); + .andExpect(jsonPath("$._links.self.href", + Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))); } -} \ No newline at end of file + +} diff --git a/dspace/config/spring/api/core-dao-services.xml b/dspace/config/spring/api/core-dao-services.xml index ae4b5e6e3b6b..bb19ed21fc61 100644 --- a/dspace/config/spring/api/core-dao-services.xml +++ b/dspace/config/spring/api/core-dao-services.xml @@ -39,6 +39,7 @@ + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index a124ec830f0c..b949eef6e1a3 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -101,6 +101,7 @@ + From 64e5697d47e9b7143cafc77afeee4ad6d6667ba9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Dec 2022 12:58:08 -0600 Subject: [PATCH 086/394] Ensure @Before and @After override default methods --- .../java/org/dspace/orcid/OrcidQueueConsumerIT.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java index e187e9a49ea6..1fe371a6a04d 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java @@ -30,7 +30,6 @@ import java.util.List; import org.dspace.AbstractIntegrationTestWithDatabase; -import org.dspace.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EntityTypeBuilder; @@ -70,7 +69,9 @@ public class OrcidQueueConsumerIT extends AbstractIntegrationTestWithDatabase { private Collection profileCollection; @Before - public void setup() { + @Override + public void setUp() throws Exception { + super.setUp(); context.turnOffAuthorisationSystem(); @@ -84,12 +85,15 @@ public void setup() { } @After - public void after() throws SQLException, AuthorizeException { + @Override + public void destroy() throws Exception { List records = orcidQueueService.findAll(context); for (OrcidQueue record : records) { orcidQueueService.delete(context, record); } context.setDispatcher(null); + + super.destroy(); } @Test From a81dd28a4602c6a87f8b1045225ba5a6aa1c2423 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Dec 2022 14:36:10 -0600 Subject: [PATCH 087/394] Ensure ItemBuilder also cleans up WorkspaceItem if it still exists --- .../src/test/java/org/dspace/builder/ItemBuilder.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java index e729982e818f..70dea309f2cf 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -397,12 +397,17 @@ public void cleanup() throws Exception { try (Context c = new Context()) { c.setDispatcher("noindex"); c.turnOffAuthorisationSystem(); + // If the workspaceItem used to create this item still exists, delete it + workspaceItem = c.reloadEntity(workspaceItem); + if (workspaceItem != null) { + workspaceItemService.deleteAll(c, workspaceItem); + } // Ensure object and any related objects are reloaded before checking to see what needs cleanup item = c.reloadEntity(item); if (item != null) { delete(c, item); - c.complete(); } + c.complete(); } } From 3fe6491975341eac6d5e22bae5db2b39e12a8d65 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Mon, 5 Dec 2022 16:27:51 +0100 Subject: [PATCH 088/394] 97183 Integration tests and fixes for SubmitFeature --- .../authorization/impl/SubmitFeature.java | 8 +- .../rest/authorization/SubmitFeatureIT.java | 328 ++++++++++++++++++ 2 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java index dce3ead8bea2..3793928fb0fe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/SubmitFeature.java @@ -13,6 +13,7 @@ import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; @@ -40,7 +41,7 @@ public class SubmitFeature implements AuthorizationFeature { @Override public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException { - if (object == null) { + if (object instanceof SiteRest) { // Check whether the user has permission to add to any collection return collectionService.countCollectionsWithSubmit("", context, null) > 0; } else if (object instanceof CollectionRest) { @@ -53,6 +54,9 @@ public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLEx @Override public String[] getSupportedTypes() { - return new String[0]; + return new String[] { + CollectionRest.CATEGORY + "." + CollectionRest.NAME, + SiteRest.CATEGORY + "." + SiteRest.NAME + }; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java new file mode 100644 index 000000000000..9404fb6620d0 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java @@ -0,0 +1,328 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.concurrent.Callable; + +import org.dspace.app.rest.authorization.impl.SubmitFeature; +import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.SiteConverter; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Site; +import org.dspace.content.service.SiteService; +import org.dspace.core.Constants; +import org.dspace.eperson.Group; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.ResultActions; + +public class SubmitFeatureIT extends AbstractControllerIntegrationTest { + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + @Autowired + private SiteService siteService; + @Autowired + private CollectionConverter collectionConverter; + @Autowired + private SiteConverter siteConverter; + @Autowired + private Utils utils; + + private Group group; + private String siteUri; + private String epersonToken; + private AuthorizationFeature submitFeature; + private Community communityA; + + private Collection collectionA1; + private Collection collectionA2; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + withSuppressedAuthorization(() -> { + communityA = CommunityBuilder.createCommunity(context).withName("Community A").build(); + collectionA1 = CollectionBuilder.createCollection(context, communityA).withName("Collection A1").build(); + collectionA2 = CollectionBuilder.createCollection(context, communityA).withName("Collection A2").build(); + group = GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + return null; + }); + submitFeature = authorizationFeatureService.find(SubmitFeature.NAME); + + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); + } + + @Test + public void testNoRights() throws Exception { + expectZeroResults(requestSitewideSubmitFeature()); + } + + @Test + public void testDirectEPersonAddPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(collectionA1) + .withAction(Constants.ADD) + .build(); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testDirectGroupAddPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(collectionA1) + .withAction(Constants.ADD) + .build(); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testDirectEPersonAdminPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(collectionA1) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testDirectGroupAdminPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(collectionA1) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testCollectionAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Collection col = CollectionBuilder + .createCollection(context, communityA) + .withName("this is another test collection") + .withAdminGroup(eperson) + .build(); + return null; + }); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testCommunityWithoutCollectionsAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains no collections") + .withAdminGroup(eperson) + .build(); + return null; + }); + expectZeroResults(requestSitewideSubmitFeature()); + } + + @Test + public void testCommunityWithCollectionsAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains a collection") + .withAdminGroup(eperson) + .build(); + Collection coll = CollectionBuilder + .createCollection(context, comm) + .withName("Contained collection") + .build(); + return null; + }); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testCommunityWithSubCommunityWithCollectionsAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Community parent = CommunityBuilder + .createCommunity(context) + .withName("This community contains no collections") + .withAdminGroup(eperson) + .build(); + Community child = CommunityBuilder + .createSubCommunity(context, parent) + .withName("This community contains a collection") + .build(); + Collection coll = CollectionBuilder + .createCollection(context, child) + .withName("Contained collection") + .build(); + return null; + }); + expectSomeResults(requestSitewideSubmitFeature()); + } + + @Test + public void testNoRightsOnCollection() throws Exception { + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testDirectEPersonWritePolicyOnCollection() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(collectionA1) + .withAction(Constants.ADD) + .build(); + expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testDirectGroupWritePolicyOnCollection() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(collectionA1) + .withAction(Constants.ADD) + .build(); + expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testDirectEPersonAdminPolicyOnCollection() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(collectionA1) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testDirectGroupAdminPolicyOnCollection() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(collectionA1) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSubmitFeature(getCollectionUri(collectionA1))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA2))); + } + + @Test + public void testCollectionAdminOnCollection() throws Exception { + Collection col = withSuppressedAuthorization(() -> { + return CollectionBuilder + .createCollection(context, communityA) + .withName("this is another test collection") + .withAdminGroup(eperson) + .build(); + }); + expectSomeResults(requestSubmitFeature(getCollectionUri(col))); + expectZeroResults(requestSubmitFeature(getCollectionUri(collectionA1))); + } + + @Test + public void testCommunityWithCollectionsAdminOnCollection() throws Exception { + Collection coll = withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains a collection") + .withAdminGroup(eperson) + .build(); + return CollectionBuilder + .createCollection(context, comm) + .withName("Contained collection") + .build(); + }); + expectSomeResults(requestSubmitFeature(getCollectionUri(coll))); + } + + @Test + public void testCommunityWithSubCommunityWithCollectionsAdminOnCollection() throws Exception { + Collection coll = withSuppressedAuthorization(() -> { + Community parent = CommunityBuilder + .createCommunity(context) + .withName("This community contains no collections") + .withAdminGroup(eperson) + .build(); + Community child = CommunityBuilder + .createSubCommunity(context, parent) + .withName("This community contains a collection") + .build(); + return CollectionBuilder + .createCollection(context, child) + .withName("Contained collection") + .build(); + }); + expectSomeResults(requestSubmitFeature(getCollectionUri(coll))); + } + + private ResultActions requestSitewideSubmitFeature() throws Exception { + return requestSubmitFeature(siteUri); + } + + private ResultActions requestSubmitFeature(String uri) throws Exception { + epersonToken = getAuthToken(eperson.getEmail(), password); + return getClient(epersonToken).perform(get("/api/authz/authorizations/search/object?") + .param("uri", uri) + .param("feature", submitFeature.getName()) + .param("embed", "feature")); + } + + private ResultActions expectSomeResults(ResultActions actions) throws Exception { + return actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + } + + private ResultActions expectZeroResults(ResultActions actions) throws Exception { + return actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + private T withSuppressedAuthorization(Callable fn) throws Exception { + context.turnOffAuthorisationSystem(); + T result = fn.call(); + context.restoreAuthSystemState(); + return result; + } + + private String getCollectionUri(Collection collection) { + CollectionRest collectionRest = collectionConverter.convert(collection, DefaultProjection.DEFAULT); + return utils.linkToSingleResource(collectionRest, "self").getHref(); + } +} From 36e9f0d4f710d39dc9a78d0cc30d97d9f1ba1468 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Mon, 5 Dec 2022 16:42:26 +0100 Subject: [PATCH 089/394] 97183 SubmitFeatureIT: renamed some tests --- .../org/dspace/app/rest/authorization/SubmitFeatureIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java index 9404fb6620d0..bba45ecd229f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/SubmitFeatureIT.java @@ -198,7 +198,7 @@ public void testNoRightsOnCollection() throws Exception { } @Test - public void testDirectEPersonWritePolicyOnCollection() throws Exception { + public void testDirectEPersonAddPolicyOnCollection() throws Exception { ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) .withUser(eperson) .withDspaceObject(collectionA1) @@ -209,7 +209,7 @@ public void testDirectEPersonWritePolicyOnCollection() throws Exception { } @Test - public void testDirectGroupWritePolicyOnCollection() throws Exception { + public void testDirectGroupAddPolicyOnCollection() throws Exception { ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) .withGroup(group) .withDspaceObject(collectionA1) From ada0b8c4d1c3e4b2df45ee22f52046b54805b420 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Mon, 5 Dec 2022 16:49:47 +0100 Subject: [PATCH 090/394] 97183 Added EditItemFeature --- .../authorization/impl/EditItemFeature.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java new file mode 100644 index 000000000000..04f95d13e186 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java @@ -0,0 +1,58 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization.impl; + +import java.sql.SQLException; + +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@AuthorizationFeatureDocumentation(name = EditItemFeature.NAME, + description = "It can be used to verify if a user has rights to edit any item.") +public class EditItemFeature implements AuthorizationFeature { + public static final String NAME = "canEdit"; + @Autowired + AuthorizeService authService; + @Autowired + ItemService itemService; + + @Autowired + Utils utils; + + @Override + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException, SearchServiceException { + if (object instanceof SiteRest) { + return itemService.countItemsWithEdit(context) > 0; + } else if (object instanceof ItemRest) { + Item item = (Item) utils.getDSpaceAPIObjectFromRest(context, object); + return authService.authorizeActionBoolean(context, item, Constants.WRITE); + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[] { + ItemRest.CATEGORY + "." + ItemRest.NAME, + SiteRest.CATEGORY + "." + SiteRest.NAME + }; + } +} From 072ae5f65e85ca5b6d3bf0f5a4c85c46b1fc666a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Dec 2022 15:09:43 -0600 Subject: [PATCH 091/394] Ensure assigned Handles have a fake prefix so they cannot overlap with incremental, autoassigned Handles --- .../dspace/orcid/OrcidQueueConsumerIT.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java index 1fe371a6a04d..f2e528d78cd6 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java @@ -143,6 +143,8 @@ public void testWithOrcidSynchronizationDisabled() throws Exception { @Test public void testOrcidQueueRecordCreationForProfile() throws Exception { + // Set a fake handle prefix for this test which we will use to assign handles below + configurationService.setProperty("handle.prefix", "fake-handle"); context.turnOffAuthorisationSystem(); Item profile = ItemBuilder.createItem(context, profileCollection) @@ -150,7 +152,7 @@ public void testOrcidQueueRecordCreationForProfile() throws Exception { .withOrcidIdentifier("0000-1111-2222-3333") .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) .withSubject("test") - .withHandle("123456789/200") + .withHandle("fake-handle/190") .withOrcidSynchronizationProfilePreference(BIOGRAPHICAL) .withOrcidSynchronizationProfilePreference(IDENTIFIERS) .build(); @@ -163,8 +165,8 @@ public void testOrcidQueueRecordCreationForProfile() throws Exception { assertThat(queueRecords, hasItem(matches(profile, profile, "KEYWORDS", null, "dc.subject::test", "test", INSERT))); assertThat(queueRecords, hasItem(matches(profile, "RESEARCHER_URLS", null, - "dc.identifier.uri::http://localhost:4000/handle/123456789/200", - "http://localhost:4000/handle/123456789/200", INSERT))); + "dc.identifier.uri::http://localhost:4000/handle/fake-handle/190", + "http://localhost:4000/handle/fake-handle/190", INSERT))); addMetadata(profile, "person", "name", "variant", "User Test", null); context.commit(); @@ -174,8 +176,8 @@ public void testOrcidQueueRecordCreationForProfile() throws Exception { assertThat(queueRecords, hasItem( matches(profile, profile, "KEYWORDS", null, "dc.subject::test", "test", INSERT))); assertThat(queueRecords, hasItem(matches(profile, "RESEARCHER_URLS", null, - "dc.identifier.uri::http://localhost:4000/handle/123456789/200", - "http://localhost:4000/handle/123456789/200", INSERT))); + "dc.identifier.uri::http://localhost:4000/handle/fake-handle/190", + "http://localhost:4000/handle/fake-handle/190", INSERT))); assertThat(queueRecords, hasItem(matches(profile, profile, "OTHER_NAMES", null, "person.name.variant::User Test", "User Test", INSERT))); } @@ -644,7 +646,8 @@ public void testNoOrcidQueueRecordCreationOccursForNotConfiguredEntities() throw @Test public void testOrcidQueueRecalculationOnProfilePreferenceUpdate() throws Exception { - + // Set a fake handle prefix for this test which we will use to assign handles below + configurationService.setProperty("handle.prefix", "fake-handle"); context.turnOffAuthorisationSystem(); Item profile = ItemBuilder.createItem(context, profileCollection) @@ -652,7 +655,7 @@ public void testOrcidQueueRecalculationOnProfilePreferenceUpdate() throws Except .withOrcidIdentifier("0000-0000-0012-2345") .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) .withSubject("Math") - .withHandle("123456789/200") + .withHandle("fake-handle/200") .withOrcidSynchronizationProfilePreference(BIOGRAPHICAL) .build(); @@ -673,8 +676,8 @@ public void testOrcidQueueRecalculationOnProfilePreferenceUpdate() throws Except assertThat(records, hasItem(matches(profile, "KEYWORDS", null, "dc.subject::Math", "Math", INSERT))); assertThat(records, hasItem(matches(profile, "EXTERNAL_IDS", null, "person.identifier.rid::ID", "ID", INSERT))); assertThat(records, hasItem(matches(profile, "RESEARCHER_URLS", null, - "dc.identifier.uri::http://localhost:4000/handle/123456789/200", - "http://localhost:4000/handle/123456789/200", INSERT))); + "dc.identifier.uri::http://localhost:4000/handle/fake-handle/200", + "http://localhost:4000/handle/fake-handle/200", INSERT))); removeMetadata(profile, "dspace", "orcid", "sync-profile"); From ab767ca6942de5fe8912270ddae3aba72c1eb591 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 5 Dec 2022 10:56:01 -0600 Subject: [PATCH 092/394] Fix flakey test in CreateMissingIdentifiersIT to ensure new curation task loads immediately --- .../general/CreateMissingIdentifiersIT.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java index 2688342358f6..2a07799deee5 100644 --- a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -17,6 +17,7 @@ import org.dspace.builder.ItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.core.factory.CoreServiceFactory; import org.dspace.curate.Curator; import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; import org.dspace.services.ConfigurationService; @@ -38,10 +39,11 @@ public class CreateMissingIdentifiersIT @Test public void testPerform() throws IOException { - ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); - configurationService.setProperty(P_TASK_DEF, null); - configurationService.addPropertyValue(P_TASK_DEF, + // Must remove any cached named plugins before creating a new one + CoreServiceFactory.getInstance().getPluginService().clearNamedPluginClasses(); + ConfigurationService configurationService = kernelImpl.getConfigurationService(); + // Define a new task dynamically + configurationService.setProperty(P_TASK_DEF, CreateMissingIdentifiers.class.getCanonicalName() + " = " + TASK_NAME); Curator curator = new Curator(); @@ -49,11 +51,11 @@ public void testPerform() context.setCurrentUser(admin); parentCommunity = CommunityBuilder.createCommunity(context) - .build(); + .build(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .build(); + .build(); Item item = ItemBuilder.createItem(context, collection) - .build(); + .build(); /* * Curate with regular test configuration -- should succeed. From 17d0fe83c440ce825e0e69bd7df72c3b434252b5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 5 Dec 2022 13:31:10 -0600 Subject: [PATCH 093/394] Fix flakey tests in ExternalSourcesRestControllerIT --- .../rest/ExternalSourcesRestControllerIT.java | 37 +++++++++---------- .../rest/matcher/ExternalSourceMatcher.java | 26 +++++++++++++ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java index cd2c2fe53db2..ce75d10ea442 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java @@ -153,23 +153,19 @@ public void findExternalSourcesByEntityTypeTest() throws Exception { getClient().perform(get("/api/integration/externalsources/search/findByEntityType") .param("entityType", "Publication")) .andExpect(status().isOk()) - // Expect *at least* 3 Publication sources - .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItems( - ExternalSourceMatcher.matchExternalSource(publicationProviders.get(0)), - ExternalSourceMatcher.matchExternalSource(publicationProviders.get(1)) - ))) + // Expect that Publication sources match (check a max of 20 as that is default page size) + .andExpect(jsonPath("$._embedded.externalsources", + ExternalSourceMatcher.matchAllExternalSources(publicationProviders, 20) + )) .andExpect(jsonPath("$.page.totalElements", Matchers.is(publicationProviders.size()))); getClient().perform(get("/api/integration/externalsources/search/findByEntityType") .param("entityType", "Journal")) .andExpect(status().isOk()) - // Expect *at least* 5 Journal sources - .andExpect(jsonPath("$._embedded.externalsources", Matchers.hasItems( - ExternalSourceMatcher.matchExternalSource(journalProviders.get(0)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(1)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(2)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(3)) - ))) + // Check that Journal sources match (check a max of 20 as that is default page size) + .andExpect(jsonPath("$._embedded.externalsources", + ExternalSourceMatcher.matchAllExternalSources(journalProviders, 20) + )) .andExpect(jsonPath("$.page.totalElements", Matchers.is(journalProviders.size()))); } @@ -193,10 +189,9 @@ public void findExternalSourcesByEntityTypePaginationTest() throws Exception { .param("entityType", "Journal") .param("size", String.valueOf(pageSize))) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( - ExternalSourceMatcher.matchExternalSource(journalProviders.get(0)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(1)) - ))) + .andExpect(jsonPath("$._embedded.externalsources", + ExternalSourceMatcher.matchAllExternalSources(journalProviders, pageSize) + )) .andExpect(jsonPath("$.page.totalPages", Matchers.is(numberOfPages))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(numJournalProviders))); @@ -205,10 +200,12 @@ public void findExternalSourcesByEntityTypePaginationTest() throws Exception { .param("page", "1") .param("size", String.valueOf(pageSize))) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.externalsources", Matchers.contains( - ExternalSourceMatcher.matchExternalSource(journalProviders.get(2)), - ExternalSourceMatcher.matchExternalSource(journalProviders.get(3)) - ))) + // Check that second page has journal sources starting at index 2. + .andExpect(jsonPath("$._embedded.externalsources", + ExternalSourceMatcher.matchAllExternalSources( + journalProviders.subList(2, journalProviders.size()), + pageSize) + )) .andExpect(jsonPath("$.page.totalPages", Matchers.is(numberOfPages))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(numJournalProviders))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ExternalSourceMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ExternalSourceMatcher.java index ce0cda0b584f..f304540eff7a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ExternalSourceMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ExternalSourceMatcher.java @@ -10,8 +10,12 @@ import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.test.AbstractControllerIntegrationTest.REST_SERVER_URL; import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; +import java.util.ArrayList; +import java.util.List; + import org.dspace.external.provider.ExternalDataProvider; import org.hamcrest.Matcher; @@ -20,6 +24,28 @@ public class ExternalSourceMatcher { private ExternalSourceMatcher() { } + /** + * Matcher which checks if all external source providers are listed (in exact order), up to the maximum number + * @param providers List of providers to check against + * @param max maximum number to check + * @return Matcher for this list of providers + */ + public static Matcher>> matchAllExternalSources( + List providers, int max) { + List matchers = new ArrayList<>(); + int count = 0; + for (ExternalDataProvider provider: providers) { + count++; + if (count > max) { + break; + } + matchers.add(matchExternalSource(provider)); + } + + // Make sure all providers exist in this exact order + return contains(matchers.toArray(new Matcher[0])); + } + public static Matcher matchExternalSource(ExternalDataProvider provider) { return matchExternalSource(provider.getSourceIdentifier(), provider.getSourceIdentifier(), false); } From 35f65ff5b8f6fff50421b5c01d414b500e56f72f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 4 Nov 2022 15:01:28 -0500 Subject: [PATCH 094/394] Add Contributing docs --- CONTRIBUTING.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 13 +------------ 2 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000000..30fd0fdfebae --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,45 @@ +# How to Contribute + +DSpace is a community built and supported project. We do not have a centralized development or support team, but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc. + +* [Contribute new code via a Pull Request](#contribute-new-code-via-a-pull-request) +* [Contribute documentation](#contribute-documentation) +* [Help others on mailing lists or Slack](#help-others-on-mailing-lists-or-slack) +* [Join a working or interest group](#join-a-working-or-interest group) + +## Contribute new code via a Pull Request + +We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone. +Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC7x/Release+Notes). + +Code Contribution Checklist +- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests) +- [ ] PRs **must** pass Checkstyle validation based on our [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide). +- [ ] PRs **must** include Javadoc for _all new/modified public methods and classes_. Larger private methods should also have Javadoc +- [ ] PRs **must** pass all automated tests and include new/updated Unit or Integration tests based on our [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). +- [ ] Basic technical documentation _should_ be provided for any new features or changes to the REST API. REST API changes should be documented in our [Rest Contract](https://github.com/DSpace/RestContract). +- [ ] If a PR includes new dependencies (in any `pom.xml`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. +- [ ] If your PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). + +Additional details on the code contribution process can be found in our [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines) + +## Contribute documentation + +DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC7x + +If you find areas of the DSpace Documentation which you wish to improve, please request a Wiki account by emailing wikihelp@lyrasis.org. +Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) or email) for access to edit our Documentation. + +## Help others on mailing lists or Slack + +DSpace has our own [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) community and [Mailing Lists](https://wiki.lyrasis.org/display/DSPACE/Mailing+Lists) where discussions take place and questions are answered. +Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via LYRASIS). + +## Join a working or interest group + +Most of the work in building/improving DSpace comes via [Working Groups](https://wiki.lyrasis.org/display/DSPACE/DSpace+Working+Groups) or [Interest Groups](https://wiki.lyrasis.org/display/DSPACE/DSpace+Interest+Groups). + +All working/interest groups are open to anyone to join and participate. A few key groups to be aware of include: + +* [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) - This is the main (mostly volunteer) development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs. +* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers. \ No newline at end of file diff --git a/README.md b/README.md index 37a46a70c96c..af9158eff361 100644 --- a/README.md +++ b/README.md @@ -48,18 +48,7 @@ See [Running DSpace 7 with Docker Compose](dspace/src/main/docker-compose/README ## Contributing -DSpace is a community built and supported project. We do not have a centralized development or support team, -but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc. - -We welcome contributions of any type. Here's a few basic guides that provide suggestions for contributing to DSpace: -* [How to Contribute to DSpace](https://wiki.lyrasis.org/display/DSPACE/How+to+Contribute+to+DSpace): How to contribute in general (via code, documentation, bug reports, expertise, etc) -* [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines): How to give back code or contribute features, bug fixes, etc. -* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team): If you are not a developer, we also have an interest group specifically for repository managers. The DCAT group meets virtually, once a month, and sends open invitations to join their meetings via the [DCAT mailing list](https://groups.google.com/d/forum/DSpaceCommunityAdvisoryTeam). - -We also encourage GitHub Pull Requests (PRs) at any time. Please see our [Development with Git](https://wiki.lyrasis.org/display/DSPACE/Development+with+Git) guide for more info. - -In addition, a listing of all known contributors to DSpace software can be -found online at: https://wiki.lyrasis.org/display/DSPACE/DSpaceContributors +See [Contributing documentation](CONTRIBUTING.md) ## Getting Help From ebccac79f7ddd9c74e0d46b2b59dbc362c225c8e Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Tue, 6 Dec 2022 09:09:39 +0100 Subject: [PATCH 095/394] 97183 EditItemFeature integration tests --- .../rest/authorization/EditItemFeatureIT.java | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java new file mode 100644 index 000000000000..4bdc7743b571 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EditItemFeatureIT.java @@ -0,0 +1,279 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.concurrent.Callable; + +import org.dspace.app.rest.authorization.impl.EditItemFeature; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.converter.SiteConverter; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.Site; +import org.dspace.content.service.SiteService; +import org.dspace.core.Constants; +import org.dspace.eperson.Group; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.ResultActions; + +public class EditItemFeatureIT extends AbstractControllerIntegrationTest { + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + @Autowired + private SiteService siteService; + @Autowired + private ItemConverter itemConverter; + @Autowired + private SiteConverter siteConverter; + @Autowired + private Utils utils; + + private Group group; + private String siteUri; + private String epersonToken; + private AuthorizationFeature editItemFeature; + private Community communityA; + + private Collection collectionA1; + private Collection collectionA2; + + private Item itemA1X; + private Item itemA2X; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + withSuppressedAuthorization(() -> { + communityA = CommunityBuilder.createCommunity(context).withName("Community A").build(); + collectionA1 = CollectionBuilder.createCollection(context, communityA).withName("Collection A1").build(); + collectionA2 = CollectionBuilder.createCollection(context, communityA).withName("Collection A2").build(); + itemA1X = ItemBuilder.createItem(context, collectionA1).withTitle("Item A1X").build(); + itemA2X = ItemBuilder.createItem(context, collectionA2).withTitle("Item A2X").build(); + group = GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + return null; + }); + editItemFeature = authorizationFeatureService.find(EditItemFeature.NAME); + + Site site = siteService.findSite(context); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); + } + + @Test + public void testNoRights() throws Exception { + expectZeroResults(requestSitewideEditItemFeature()); + } + + @Test + public void testDirectEPersonWritePolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(itemA1X) + .withAction(Constants.WRITE) + .build(); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testDirectGroupWritePolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(itemA1X) + .withAction(Constants.WRITE) + .build(); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testDirectEPersonAdminPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withUser(eperson) + .withDspaceObject(itemA1X) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testDirectGroupAdminPolicy() throws Exception { + ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) + .withGroup(group) + .withDspaceObject(itemA1X) + .withAction(Constants.ADMIN) + .build(); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testNonemptyCollectionAdmin() throws Exception { + Item item = withSuppressedAuthorization(() -> { + Collection col = CollectionBuilder + .createCollection(context, communityA) + .withName("nonempty collection") + .withAdminGroup(eperson) + .build(); + return ItemBuilder + .createItem(context, col) + .withTitle("item in nonempty collection") + .build(); + }); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(item)); + expectZeroResults(requestEditItemFeature(itemA1X)); + expectZeroResults(requestEditItemFeature(itemA2X)); + } + + @Test + public void testEmptyCollectionAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Collection col = CollectionBuilder + .createCollection(context, communityA) + .withName("nonempty collection") + .withAdminGroup(eperson) + .build(); + return null; + }); + expectZeroResults(requestSitewideEditItemFeature()); + } + + @Test + public void testCommunityWithEmptyCollectionAdmin() throws Exception { + withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains a collection") + .withAdminGroup(eperson) + .build(); + Collection coll = CollectionBuilder + .createCollection(context, comm) + .withName("This collection contains no items") + .build(); + return null; + }); + expectZeroResults(requestSitewideEditItemFeature()); + } + + @Test + public void testCommunityWithNonemptyCollectionAdmin() throws Exception { + Item item = withSuppressedAuthorization(() -> { + Community comm = CommunityBuilder + .createCommunity(context) + .withName("This community contains a collection") + .withAdminGroup(eperson) + .build(); + Collection coll = CollectionBuilder + .createCollection(context, comm) + .withName("This collection contains an item") + .build(); + return ItemBuilder + .createItem(context, coll) + .withTitle("This is an item") + .build(); + }); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(item)); + } + + @Test + public void testNestedCommunitiesWithNonemptyCollectionAdmin() throws Exception { + Item item = withSuppressedAuthorization(() -> { + Community parent = CommunityBuilder + .createCommunity(context) + .withName("parent community") + .withAdminGroup(eperson) + .build(); + Community child = CommunityBuilder + .createSubCommunity(context, parent) + .withName("child community") + .withAdminGroup(eperson) + .build(); + Collection coll = CollectionBuilder + .createCollection(context, child) + .withName("This collection contains an item") + .build(); + return ItemBuilder + .createItem(context, coll) + .withTitle("This is an item") + .build(); + }); + expectSomeResults(requestSitewideEditItemFeature()); + expectSomeResults(requestEditItemFeature(item)); + } + + private ResultActions requestSitewideEditItemFeature() throws Exception { + return requestEditItemFeature(siteUri); + } + + private ResultActions requestEditItemFeature(Item item) throws Exception { + return requestEditItemFeature(getItemUri(item)); + } + private ResultActions requestEditItemFeature(String uri) throws Exception { + epersonToken = getAuthToken(eperson.getEmail(), password); + return getClient(epersonToken).perform(get("/api/authz/authorizations/search/object?") + .param("uri", uri) + .param("feature", editItemFeature.getName()) + .param("embed", "feature")); + } + + private ResultActions expectSomeResults(ResultActions actions) throws Exception { + return actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + } + + private ResultActions expectZeroResults(ResultActions actions) throws Exception { + return actions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + private T withSuppressedAuthorization(Callable fn) throws Exception { + context.turnOffAuthorisationSystem(); + T result = fn.call(); + context.restoreAuthSystemState(); + return result; + } + + private String getItemUri(Item item) { + ItemRest itemRest = itemConverter.convert(item, DefaultProjection.DEFAULT); + return utils.linkToSingleResource(itemRest, "self").getHref(); + } +} From f928ec71f60543c04ab9c206faaeb7c5a12969c7 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Tue, 6 Dec 2022 16:12:38 +0100 Subject: [PATCH 096/394] 97183 Renamed "canEditItem" feature --- .../org/dspace/app/rest/authorization/impl/EditItemFeature.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java index 04f95d13e186..5c605daaf407 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditItemFeature.java @@ -28,7 +28,7 @@ @AuthorizationFeatureDocumentation(name = EditItemFeature.NAME, description = "It can be used to verify if a user has rights to edit any item.") public class EditItemFeature implements AuthorizationFeature { - public static final String NAME = "canEdit"; + public static final String NAME = "canEditItem"; @Autowired AuthorizeService authService; @Autowired From a501d840920f9bbe68f20a5763f86be9841d485f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 6 Dec 2022 10:48:42 -0600 Subject: [PATCH 097/394] Minor fixes to sync CONTRIBUTING with PR template --- .github/pull_request_template.md | 10 ++++++---- CONTRIBUTING.md | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ea5c53564741..5b3f4336e6a2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ ## References _Add references/links to any related issues or PRs. These may include:_ -* Fixes #[issue-number] -* Related to [REST Contract](https://github.com/DSpace/Rest7Contract) +* Fixes #`issue-number` (if this fixes an issue ticket) +* Related to DSpace/RestContract#`pr-number` (if a corresponding REST Contract PR exists) ## Description Short summary of changes (1-2 sentences). @@ -22,5 +22,7 @@ _This checklist provides a reminder of what we are going to look for when review - [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide). - [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods. - [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). -- [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. -- [ ] If my PR modifies the REST API, I've opened a separate [REST Contract](https://github.com/DSpace/RestContract/blob/main/README.md) PR related to this change. +- [ ] If my PR includes new libraries/dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. +- [ ] If my PR modifies REST API endpoints, I've opened a separate [REST Contract](https://github.com/DSpace/RestContract/blob/main/README.md) PR related to this change. +- [ ] If my PR includes new configurations, I've provided basic technical documentation in the PR itself. +- [ ] If my PR fixes an issue ticket, I've [linked them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30fd0fdfebae..45a6af9ce5a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ DSpace is a community built and supported project. We do not have a centralized * [Contribute new code via a Pull Request](#contribute-new-code-via-a-pull-request) * [Contribute documentation](#contribute-documentation) * [Help others on mailing lists or Slack](#help-others-on-mailing-lists-or-slack) -* [Join a working or interest group](#join-a-working-or-interest group) +* [Join a working or interest group](#join-a-working-or-interest-group) ## Contribute new code via a Pull Request @@ -17,9 +17,9 @@ Code Contribution Checklist - [ ] PRs **must** pass Checkstyle validation based on our [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide). - [ ] PRs **must** include Javadoc for _all new/modified public methods and classes_. Larger private methods should also have Javadoc - [ ] PRs **must** pass all automated tests and include new/updated Unit or Integration tests based on our [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). +- [ ] If a PR includes new libraries/dependencies (in any `pom.xml`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. - [ ] Basic technical documentation _should_ be provided for any new features or changes to the REST API. REST API changes should be documented in our [Rest Contract](https://github.com/DSpace/RestContract). -- [ ] If a PR includes new dependencies (in any `pom.xml`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. -- [ ] If your PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). +- [ ] If a PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). Additional details on the code contribution process can be found in our [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines) From 9cd358f7b2201e3e88a65b84bcf961e1a2cb8826 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 25 Oct 2022 15:01:06 -0500 Subject: [PATCH 098/394] Update to use GitHub CodeQL for code scanning --- .github/workflows/codescan.yml | 59 ++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/codescan.yml diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml new file mode 100644 index 000000000000..7580b4ba3dc3 --- /dev/null +++ b/.github/workflows/codescan.yml @@ -0,0 +1,59 @@ +# DSpace CodeQL code scanning configuration for GitHub +# https://docs.github.com/en/code-security/code-scanning +# +# NOTE: Code scanning must be run separate from our default build.yml +# because CodeQL requires a fresh build with all tests *disabled*. +name: "Code Scanning" + +# Run this code scan for all pushes / PRs to main branch. Also run once a week. +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + # Don't run if PR is only updating static documentation + paths-ignore: + - '**/*.md' + - '**/*.txt' + schedule: + - cron: "37 0 * * 1" + +jobs: + analyze: + name: Analyze Code + runs-on: ubuntu-latest + # Limit permissions of this GitHub action. Can only write to security-events + permissions: + actions: read + contents: read + security-events: write + + steps: + # https://github.com/actions/checkout + - name: Checkout repository + uses: actions/checkout@v3 + + # https://github.com/actions/setup-java + - name: Install JDK + uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: 'temurin' + + # Initializes the CodeQL tools for scanning. + # https://github.com/github/codeql-action + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + # Codescan Javascript as well since a few JS files exist in REST API's interface + languages: java, javascript + + # Autobuild attempts to build any compiled languages + # NOTE: Based on testing, this autobuild process works well for DSpace. A custom + # DSpace build w/caching (like in build.yml) was about the same speed as autobuild. + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # Perform GitHub Code Scanning. + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 From 18ce42c38884fb8ef4826735814bc81d4a30721a Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 13 Jul 2022 14:56:38 -0700 Subject: [PATCH 099/394] Updates the canvas dimension method to use multiple bundles. --- .../dspace/iiif/IIIFApiQueryServiceImpl.java | 3 +- .../app/iiif/service/CanvasService.java | 40 ++++++++++++------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java index 04a08a7781a2..7c6336ed3c7f 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/iiif/IIIFApiQueryServiceImpl.java @@ -23,8 +23,7 @@ /** - * Queries the configured IIIF server for image dimensions. Used for - * formats that cannot be easily read using ImageIO (jpeg 2000). + * Queries the configured IIIF image server via the Image API. * * @author Michael Spalti mspalti@willamette.edu */ diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java index 189e4d6f62ca..faa1c7e9b123 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java @@ -78,29 +78,39 @@ public CanvasService(ConfigurationService configurationService) { } /** - * Checks for bitstream iiif.image.width metadata in the first - * bitstream in first IIIF bundle. If bitstream metadata is not - * found, use the IIIF image service to update the default canvas - * dimensions for this request. Called once for each manifest. + * Checks for "iiif.image.width" metadata in the first + * bitstream of IIIF bundles. If bitstream metadata is not + * found, sets the default canvas dimensions for this request, + * using the IIIF image service to get the dimensions. Called + * once for each manifest. * @param bundles IIIF bundles for this item */ protected void guessCanvasDimensions(List bundles) { - Bitstream firstBistream = bundles.get(0).getBitstreams().get(0); - if (!utils.hasWidthMetadata(firstBistream)) { - int[] imageDims = utils.getImageDimensions(firstBistream); - if (imageDims != null && imageDims.length == 2) { - // update the fallback dimensions - defaultCanvasWidthFallback = imageDims[0]; - defaultCanvasHeightFallback = imageDims[1]; + // prevent redundant updates. + boolean dimensionUpdated = false; + for (Bundle bundle : bundles) { + if(bundle.getBitstreams().size() > 0 && !dimensionUpdated) { + Bitstream firstBitstream = bundle.getBitstreams().get(0); + if (!utils.hasWidthMetadata(firstBitstream)) { + // get the dimensions of the first image. + int[] imageDims = utils.getImageDimensions(firstBitstream); + if (imageDims != null && imageDims.length == 2) { + // update the fallback dimensions + defaultCanvasWidthFallback = imageDims[0]; + defaultCanvasHeightFallback = imageDims[1]; + } + setDefaultCanvasDimensions(); + // the default dimension for this request has been updated. + dimensionUpdated = true; + } } - setDefaultCanvasDimensions(); } } /** - * Used to set the height and width dimensions for all images when iiif.image.default-width and - * iiif.image.default-height are set to -1 in DSpace configuration. - * The values are updated only if the bitstream does not have its own iiif.image.width metadata. + * Sets the height and width dimensions for all images when "iiif.image.default-width" + * and "iiif.image.default-height" are set to -1 in DSpace configuration. The values + * are updated only when the bitstream does not have its own image dimension metadata. * @param bitstream */ private void setCanvasDimensions(Bitstream bitstream) { From d4c03764a661f948d3afca8dbde6903b0d9370fb Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 13 Jul 2022 15:38:09 -0700 Subject: [PATCH 100/394] Added check for image format. Added logging for error. --- .../app/iiif/service/CanvasService.java | 49 ++++++++++++------- .../app/iiif/service/ManifestService.java | 2 +- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java index faa1c7e9b123..52dab5ef02c3 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java @@ -9,6 +9,7 @@ import static org.dspace.app.iiif.service.utils.IIIFUtils.METADATA_IMAGE_WIDTH; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -78,30 +79,42 @@ public CanvasService(ConfigurationService configurationService) { } /** - * Checks for "iiif.image.width" metadata in the first - * bitstream of IIIF bundles. If bitstream metadata is not - * found, sets the default canvas dimensions for this request, - * using the IIIF image service to get the dimensions. Called - * once for each manifest. + * Checks for "iiif.image.width" metadata in IIIF bundles. When bitstream + * metadata is not found for the first image in the bundle this method updates the + * default canvas dimensions for the request based on the actual image dimensions, + * using the IIIF image service. Called once for each manifest. * @param bundles IIIF bundles for this item */ - protected void guessCanvasDimensions(List bundles) { + protected void guessCanvasDimensions(List bundles, Context context) { // prevent redundant updates. boolean dimensionUpdated = false; + for (Bundle bundle : bundles) { - if(bundle.getBitstreams().size() > 0 && !dimensionUpdated) { - Bitstream firstBitstream = bundle.getBitstreams().get(0); - if (!utils.hasWidthMetadata(firstBitstream)) { - // get the dimensions of the first image. - int[] imageDims = utils.getImageDimensions(firstBitstream); - if (imageDims != null && imageDims.length == 2) { - // update the fallback dimensions - defaultCanvasWidthFallback = imageDims[0]; - defaultCanvasHeightFallback = imageDims[1]; + if (!dimensionUpdated) { + for (Bitstream bitstream : bundle.getBitstreams()) { + boolean isImage = false; + try { + isImage = bitstream.getFormat(context).getMIMEType().contains("image/"); + } catch (SQLException e) { + log.warn("Error reading the bitstream format: " + e.getMessage()); + } + if (isImage) { + // check for width dimension + if (!utils.hasWidthMetadata(bitstream)) { + // get the dimensions of the image. + int[] imageDims = utils.getImageDimensions(bitstream); + if (imageDims != null && imageDims.length == 2) { + // update the fallback dimensions + defaultCanvasWidthFallback = imageDims[0]; + defaultCanvasHeightFallback = imageDims[1]; + } + setDefaultCanvasDimensions(); + // stop processing the bundles + dimensionUpdated = true; + } + // check only the first image + break; } - setDefaultCanvasDimensions(); - // the default dimension for this request has been updated. - dimensionUpdated = true; } } } diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java index c12365a47dda..9a4b57555f87 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java @@ -156,7 +156,7 @@ private void addCanvasAndRange(Context context, Item item, String manifestId) { List bundles = utils.getIIIFBundles(item); // Set the default canvas dimensions. if (guessCanvasDimension) { - canvasService.guessCanvasDimensions(bundles); + canvasService.guessCanvasDimensions(bundles, context); } for (Bundle bnd : bundles) { String bundleToCPrefix = null; From 37e89c13b18579acae88b8893888ff8ecbec7089 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 28 Sep 2022 16:42:13 -0700 Subject: [PATCH 101/394] New mock and test. --- .../config/spring/api/iiif-processing.xml | 9 ++++ .../app/rest/iiif/IIIFControllerIT.java | 48 ++++++++++++++++++- .../iiif/MockIIIFApiQueryServiceImpl.java | 16 +++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml create mode 100644 dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml new file mode 100644 index 000000000000..06b0a9af0421 --- /dev/null +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java index 08ed91500d76..b4d1f785d4c9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java @@ -22,12 +22,14 @@ import org.apache.commons.io.IOUtils; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.BitstreamBuilder; +import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.service.ItemService; @@ -85,7 +87,7 @@ public void notFoundTest() throws Exception { } @Test - public void findOneIIIFSearchableEntityTypeWithGlobalConfigIT() throws Exception { + public void findOneIIIFSearchableItemWithDefaultDimensionsIT() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -138,7 +140,8 @@ public void findOneIIIFSearchableEntityTypeWithGlobalConfigIT() throws Exception .andExpect(jsonPath("$.sequences[0].canvases[0].@id", Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(2200))) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(64))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(64))) .andExpect(jsonPath("$.sequences[0].canvases[0].images[0].resource.service.@id", Matchers.endsWith(bitstream1.getID().toString()))) .andExpect(jsonPath("$.sequences[0].canvases[0].metadata[0].label", is("File name"))) @@ -1290,4 +1293,45 @@ public void findOneWithCacheEvictionAfterItemUpdate() throws Exception { .andExpect(jsonPath("$.metadata[0].value", is("Public item (revised)"))); } + @Test + public void setDefaultCanvasDimensionCustomBundle() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") + .build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .enableIIIF() + .build(); + + + Bundle targetBundle = BundleBuilder.createBundle(context, publicItem1) + .withName(IIIFBundle) + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + Bitstream bitstream1 = BitstreamBuilder + .createBitstream(context, targetBundle, is) + .withName("Bitstream1.jpg") + .withMimeType("image/jpeg") + .build(); + } + context.restoreAuthSystemState(); + + // canvas dimensions using bitstream in the custom bundle (no bitstreams in ORIGINAL) + getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(64))) + .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(64))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java b/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java new file mode 100644 index 000000000000..662e06cd0b06 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java @@ -0,0 +1,16 @@ +/** + * 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.iiif; + +import org.dspace.content.Bitstream; + +public class MockIIIFApiQueryServiceImpl extends IIIFApiQueryServiceImpl { + public int[] getImageDimensions(Bitstream bitstream) { + return new int[]{64, 64}; + } +} From 83b1b346c958b49351f97a699046e5f585b22a37 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 28 Sep 2022 16:42:58 -0700 Subject: [PATCH 102/394] Updating to address feedback. --- .../org/dspace/app/iiif/service/CanvasService.java | 11 ++--------- .../org/dspace/app/iiif/service/ManifestService.java | 2 +- .../org/dspace/app/iiif/service/utils/IIIFUtils.java | 4 ++-- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java index 52dab5ef02c3..dcfb707d62a8 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/CanvasService.java @@ -9,7 +9,6 @@ import static org.dspace.app.iiif.service.utils.IIIFUtils.METADATA_IMAGE_WIDTH; -import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -85,20 +84,14 @@ public CanvasService(ConfigurationService configurationService) { * using the IIIF image service. Called once for each manifest. * @param bundles IIIF bundles for this item */ - protected void guessCanvasDimensions(List bundles, Context context) { + protected void guessCanvasDimensions(Context context, List bundles) { // prevent redundant updates. boolean dimensionUpdated = false; for (Bundle bundle : bundles) { if (!dimensionUpdated) { for (Bitstream bitstream : bundle.getBitstreams()) { - boolean isImage = false; - try { - isImage = bitstream.getFormat(context).getMIMEType().contains("image/"); - } catch (SQLException e) { - log.warn("Error reading the bitstream format: " + e.getMessage()); - } - if (isImage) { + if (utils.isIIIFBitstream(context, bitstream)) { // check for width dimension if (!utils.hasWidthMetadata(bitstream)) { // get the dimensions of the image. diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java index 9a4b57555f87..09526deeb6cb 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/ManifestService.java @@ -156,7 +156,7 @@ private void addCanvasAndRange(Context context, Item item, String manifestId) { List bundles = utils.getIIIFBundles(item); // Set the default canvas dimensions. if (guessCanvasDimension) { - canvasService.guessCanvasDimensions(bundles, context); + canvasService.guessCanvasDimensions(context, bundles); } for (Bundle bnd : bundles) { String bundleToCPrefix = null; diff --git a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java index 0ef759daf8c8..782a5a985292 100644 --- a/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java +++ b/dspace-iiif/src/main/java/org/dspace/app/iiif/service/utils/IIIFUtils.java @@ -137,7 +137,7 @@ public List getIIIFBitstreams(Context context, Bundle bundle) { * @param b the DSpace bitstream to check * @return true if the bitstream can be used as IIIF resource */ - private boolean isIIIFBitstream(Context context, Bitstream b) { + public boolean isIIIFBitstream(Context context, Bitstream b) { return checkImageMimeType(getBitstreamMimeType(b, context)) && b.getMetadata().stream() .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED)) .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no")); @@ -228,7 +228,7 @@ public String asJson(Resource resource) { * @param mimetype * @return true if an image */ - public boolean checkImageMimeType(String mimetype) { + private boolean checkImageMimeType(String mimetype) { if (mimetype != null && mimetype.contains("image/")) { return true; } From b0f809db0501fcbcdc0c5c7344dcb3b89b334f09 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 28 Sep 2022 17:02:17 -0700 Subject: [PATCH 103/394] Added mock to dspace-api tests. Updated bean configs. --- .../config/spring/api/iiif-processing.xml | 12 ++++++++++++ .../dspace/iiif/MockIIIFApiQueryServiceImpl.java | 16 ++++++++++++++++ .../config/spring/api/iiif-processing.xml | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml create mode 100644 dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml new file mode 100644 index 000000000000..ea2c6546085b --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java b/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java new file mode 100644 index 000000000000..662e06cd0b06 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java @@ -0,0 +1,16 @@ +/** + * 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.iiif; + +import org.dspace.content.Bitstream; + +public class MockIIIFApiQueryServiceImpl extends IIIFApiQueryServiceImpl { + public int[] getImageDimensions(Bitstream bitstream) { + return new int[]{64, 64}; + } +} diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml index 06b0a9af0421..462fc23a0d23 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/iiif-processing.xml @@ -4,6 +4,6 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" default-lazy-init="true"> - + \ No newline at end of file From 34e6ce3b39e6b5f6389d649f6d301acd95483809 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 29 Sep 2022 08:12:08 -0700 Subject: [PATCH 104/394] Missing javadoc. --- .../java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java | 4 ++++ .../java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java b/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java index 662e06cd0b06..a240e76f9792 100644 --- a/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java +++ b/dspace-api/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java @@ -9,6 +9,10 @@ import org.dspace.content.Bitstream; +/** + * Mock for the IIIFApiQueryService. + * @author Michael Spalti (mspalti at willamette.edu) + */ public class MockIIIFApiQueryServiceImpl extends IIIFApiQueryServiceImpl { public int[] getImageDimensions(Bitstream bitstream) { return new int[]{64, 64}; diff --git a/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java b/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java index 662e06cd0b06..a240e76f9792 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java +++ b/dspace-server-webapp/src/test/java/org/dspace/iiif/MockIIIFApiQueryServiceImpl.java @@ -9,6 +9,10 @@ import org.dspace.content.Bitstream; +/** + * Mock for the IIIFApiQueryService. + * @author Michael Spalti (mspalti at willamette.edu) + */ public class MockIIIFApiQueryServiceImpl extends IIIFApiQueryServiceImpl { public int[] getImageDimensions(Bitstream bitstream) { return new int[]{64, 64}; From e6693f4232dc495aac8d692a396fd4d187a90f8c Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Sun, 27 Nov 2022 13:27:50 +0300 Subject: [PATCH 105/394] dspace-api: tell ImageMagick about the PDF CropBox ImageMagick uses the MediaBox by default when rasterizing PDFs be- cause the PDF specification says that all PDFs *must* contain one. This page box is the parent for all other boxes that a PDF *may* contain, for example a CropBox, ArtBox, etc. In many cases these are the same, but when they are not the CropBox is used to define the area displayed to a user when they open the PDF on screen (as opposed to when printing on paper). If a PDF has a CropBox that is different to its MediaBox then we should tell ImageMagick to use it. Fixes: https://github.com/DSpace/DSpace/issues/8549 --- .../ImageMagickThumbnailFilter.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java index 862943a30c2b..d16243e3e3bc 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java @@ -14,6 +14,9 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Item; @@ -132,6 +135,26 @@ public File getImageFile(File f, int page, boolean verbose) op.density(Integer.valueOf(density)); } + // Check the PDF's MediaBox and CropBox to see if they are the same. + // If not, then tell ImageMagick to use the CropBox when generating + // the thumbnail because the CropBox is generally used to define the + // area displayed when a user opens the PDF on a screen, whereas the + // MediaBox is used for print. Not all PDFs set these correctly, so + // we can use ImageMagick's default behavior unless we see an explit + // CropBox. Note: we don't need to do anything special to detect if + // the CropBox is missing or empty because pdfbox will set it to the + // same size as the MediaBox if it doesn't exist. Also note that we + // only need to check the first page, since that's what we use for + // generating the thumbnail (PDDocument uses a zero-based index). + PDPage pdfPage = PDDocument.load(f).getPage(0); + PDRectangle pdfPageMediaBox = pdfPage.getMediaBox(); + PDRectangle pdfPageCropBox = pdfPage.getCropBox(); + + // This option must come *before* we open the input file. + if (pdfPageCropBox != pdfPageMediaBox) { + op.define("pdf:use-cropbox=true"); + } + String s = "[" + page + "]"; op.addImage(f.getAbsolutePath() + s); if (configurationService.getBooleanProperty(PRE + ".flatten", true)) { From 088bbc80533be38065bbb165584472383bb7ef57 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Wed, 7 Dec 2022 14:23:01 +0100 Subject: [PATCH 106/394] 97080: Fix facet search box should search all parts (WIP) --- .../org/dspace/discovery/SolrServiceImpl.java | 4 +- .../indexobject/ItemIndexFactoryImpl.java | 213 +++++++++++------- 2 files changed, 134 insertions(+), 83 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index f894553e5d84..d64451def341 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -1250,9 +1250,9 @@ public String toSortFieldIndex(String metadataField, String type) { protected String transformFacetField(DiscoverFacetField facetFieldConfig, String field, boolean removePostfix) { if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) { if (removePostfix) { - return field.substring(0, field.lastIndexOf("_filter")); + return field.substring(0, field.lastIndexOf("_prefix")); } else { - return field + "_filter"; + return field + "_prefix"; } } else if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { if (removePostfix) { diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index e9f18ae949ab..85c3939ac61a 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -21,6 +21,8 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -468,88 +470,10 @@ public void addDiscoveryFields(SolrInputDocument doc, Context context, Item item + var); } } - + // if searchFilter is of type "facet", delegate to indexFacetFilters method if (searchFilter.getFilterType().equals(DiscoverySearchFilterFacet.FILTER_TYPE_FACET)) { - if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) { - //Add a special filter - //We use a separator to split up the lowercase and regular case, this is needed to - // get our filters in regular case - //Solr has issues with facet prefix and cases - if (authority != null) { - String facetValue = preferedLabel != null ? preferedLabel : value; - doc.addField(searchFilter.getIndexFieldName() + "_filter", facetValue - .toLowerCase() + separator + facetValue + SearchUtils.AUTHORITY_SEPARATOR - + authority); - } else { - doc.addField(searchFilter.getIndexFieldName() + "_filter", - value.toLowerCase() + separator + value); - } - } else if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { - if (date != null) { - String indexField = searchFilter.getIndexFieldName() + ".year"; - String yearUTC = DateFormatUtils.formatUTC(date, "yyyy"); - doc.addField(searchFilter.getIndexFieldName() + "_keyword", yearUTC); - // add the year to the autocomplete index - doc.addField(searchFilter.getIndexFieldName() + "_ac", yearUTC); - doc.addField(indexField, yearUTC); - - if (yearUTC.startsWith("0")) { - doc.addField( - searchFilter.getIndexFieldName() - + "_keyword", - yearUTC.replaceFirst("0*", "")); - // add date without starting zeros for autocomplete e filtering - doc.addField( - searchFilter.getIndexFieldName() - + "_ac", - yearUTC.replaceFirst("0*", "")); - doc.addField( - searchFilter.getIndexFieldName() - + "_ac", - value.replaceFirst("0*", "")); - doc.addField( - searchFilter.getIndexFieldName() - + "_keyword", - value.replaceFirst("0*", "")); - } - - //Also save a sort value of this year, this is required for determining the upper - // & lower bound year of our facet - if (doc.getField(indexField + "_sort") == null) { - //We can only add one year so take the first one - doc.addField(indexField + "_sort", yearUTC); - } - } - } else if (searchFilter.getType() - .equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) { - HierarchicalSidebarFacetConfiguration hierarchicalSidebarFacetConfiguration = - (HierarchicalSidebarFacetConfiguration) searchFilter; - String[] subValues = value.split(hierarchicalSidebarFacetConfiguration.getSplitter()); - if (hierarchicalSidebarFacetConfiguration - .isSkipFirstNodeLevel() && 1 < subValues.length) { - //Remove the first element of our array - subValues = (String[]) ArrayUtils.subarray(subValues, 1, subValues.length); - } - for (int i = 0; i < subValues.length; i++) { - StringBuilder valueBuilder = new StringBuilder(); - for (int j = 0; j <= i; j++) { - valueBuilder.append(subValues[j]); - if (j < i) { - valueBuilder.append(hierarchicalSidebarFacetConfiguration.getSplitter()); - } - } - - String indexValue = valueBuilder.toString().trim(); - doc.addField(searchFilter.getIndexFieldName() + "_tax_" + i + "_filter", - indexValue.toLowerCase() + separator + indexValue); - //We add the field x times that it has occurred - for (int j = i; j < subValues.length; j++) { - doc.addField(searchFilter.getIndexFieldName() + "_filter", - indexValue.toLowerCase() + separator + indexValue); - doc.addField(searchFilter.getIndexFieldName() + "_keyword", indexValue); - } - } - } + indexIfFilterTypeFacet(doc, searchFilter, value, date, + authority, preferedLabel, separator); } } } @@ -740,4 +664,131 @@ public Optional findIndexableObject(Context context, String id) t final Item item = itemService.find(context, UUID.fromString(id)); return item == null ? Optional.empty() : Optional.of(new IndexableItem(item)); } + + /** + * Handles indexing when discoverySearchFilter is of type facet. + * + * @param doc the solr document + * @param searchFilter the discoverySearchFilter + * @param value the metadata value + * @param date Date object + * @param authority the authority key + * @param preferedLabel the preferred label for metadata field + * @param separator the separator being used to separate lowercase and regular case + */ + private void indexIfFilterTypeFacet(SolrInputDocument doc, DiscoverySearchFilter searchFilter, String value, + Date date, String authority, String preferedLabel, String separator) { + if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) { + //Add a special filter + //We use a separator to split up the lowercase and regular case, this is needed to + // get our filters in regular case + //Solr has issues with facet prefix and cases + if (authority != null) { + String facetValue = preferedLabel != null ? preferedLabel : value; + doc.addField(searchFilter.getIndexFieldName() + "_filter", facetValue + .toLowerCase() + separator + facetValue + SearchUtils.AUTHORITY_SEPARATOR + + authority); + } else { + doc.addField(searchFilter.getIndexFieldName() + "_filter", + value.toLowerCase() + separator + value); + } + //Also add prefix field with all parts of value + saveFacetPrefixParts(doc, searchFilter, value, separator); + } else if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { + if (date != null) { + String indexField = searchFilter.getIndexFieldName() + ".year"; + String yearUTC = DateFormatUtils.formatUTC(date, "yyyy"); + doc.addField(searchFilter.getIndexFieldName() + "_keyword", yearUTC); + // add the year to the autocomplete index + doc.addField(searchFilter.getIndexFieldName() + "_ac", yearUTC); + doc.addField(indexField, yearUTC); + + if (yearUTC.startsWith("0")) { + doc.addField( + searchFilter.getIndexFieldName() + + "_keyword", + yearUTC.replaceFirst("0*", "")); + // add date without starting zeros for autocomplete e filtering + doc.addField( + searchFilter.getIndexFieldName() + + "_ac", + yearUTC.replaceFirst("0*", "")); + doc.addField( + searchFilter.getIndexFieldName() + + "_ac", + value.replaceFirst("0*", "")); + doc.addField( + searchFilter.getIndexFieldName() + + "_keyword", + value.replaceFirst("0*", "")); + } + + //Also save a sort value of this year, this is required for determining the upper + // & lower bound year of our facet + if (doc.getField(indexField + "_sort") == null) { + //We can only add one year so take the first one + doc.addField(indexField + "_sort", yearUTC); + } + } + } else if (searchFilter.getType() + .equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) { + HierarchicalSidebarFacetConfiguration hierarchicalSidebarFacetConfiguration = + (HierarchicalSidebarFacetConfiguration) searchFilter; + String[] subValues = value.split(hierarchicalSidebarFacetConfiguration.getSplitter()); + if (hierarchicalSidebarFacetConfiguration + .isSkipFirstNodeLevel() && 1 < subValues.length) { + //Remove the first element of our array + subValues = (String[]) ArrayUtils.subarray(subValues, 1, subValues.length); + } + for (int i = 0; i < subValues.length; i++) { + StringBuilder valueBuilder = new StringBuilder(); + for (int j = 0; j <= i; j++) { + valueBuilder.append(subValues[j]); + if (j < i) { + valueBuilder.append(hierarchicalSidebarFacetConfiguration.getSplitter()); + } + } + + String indexValue = valueBuilder.toString().trim(); + doc.addField(searchFilter.getIndexFieldName() + "_tax_" + i + "_filter", + indexValue.toLowerCase() + separator + indexValue); + //We add the field x times that it has occurred + for (int j = i; j < subValues.length; j++) { + doc.addField(searchFilter.getIndexFieldName() + "_filter", + indexValue.toLowerCase() + separator + indexValue); + doc.addField(searchFilter.getIndexFieldName() + "_keyword", indexValue); + } + } + } + } + + /** + * Stores every "value part" in lowercase, together with the original value in regular case, + * separated by the separator, in the {fieldName}_prefix field. + *
+ * E.g. Author "With Multiple Words" gets stored as: + *
+ * + * with multiple words ||| With Multiple Words,
+ * multiple words ||| With Multiple Words,
+ * words ||| With Multiple Words,
+ *
+ * in the author_prefix field. + * @param doc the solr document + * @param searchFilter the current discoverySearchFilter + * @param value the metadata value + * @param separator the separator being used to separate value part and original value + */ + private void saveFacetPrefixParts(SolrInputDocument doc, DiscoverySearchFilter searchFilter, + String value, String separator) { + value = StringUtils.normalizeSpace(value); + Pattern pattern = Pattern.compile("\\b\\w+\\b", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(value); + while (matcher.find()) { + int index = matcher.start(); + String currentPart = StringUtils.substring(value, index); + doc.addField(searchFilter.getIndexFieldName() + "_prefix", + currentPart.toLowerCase() + separator + value); + } + } } From fd7016f14e190b9f0ea801edc57e5ea437549c4d Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 7 Dec 2022 14:25:35 +0100 Subject: [PATCH 107/394] [CST-7756] refactoring --- .../factory/ContentServiceFactory.java | 1 - .../SubscribeParameterServiceImpl.java | 19 +- .../dspace/eperson/SubscribeServiceImpl.java | 119 +-- .../dspace/eperson/SubscriptionParameter.java | 7 +- .../eperson/dao/impl/SubscriptionDAOImpl.java | 4 +- .../eperson/service/SubscribeService.java | 42 +- .../service/SubscriptionParameterService.java | 14 +- ...mter_change_columns_subscription_table.sql | 2 +- .../test/data/dspaceFolder/config/local.cfg | 2 +- .../org/dspace/builder/SubscribeBuilder.java | 39 +- .../rest/converter/SubscriptionConverter.java | 2 +- .../rest/model/SubscriptionParameterRest.java | 9 +- ...ubscriptionDSpaceObjectLinkRepository.java | 2 - .../SubscriptionEPersonLinkRepository.java | 2 - .../SubscriptionRestRepository.java | 214 +++--- .../SubscriptionParameterAddOperation.java | 79 -- .../SubscriptionParameterRemoveOperation.java | 65 -- ...SubscriptionParameterReplaceOperation.java | 83 --- ...dminAndOwnerPermissionEvaluatorPlugin.java | 73 ++ ...criptionRestPermissionEvaluatorPlugin.java | 84 +++ .../rest/SubscriptionRestRepositoryIT.java | 705 +++++++++--------- .../app/rest/matcher/SubscriptionMatcher.java | 57 ++ 22 files changed, 777 insertions(+), 847 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java index 2335af007ff2..8a83976b6662 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java @@ -119,7 +119,6 @@ public InProgressSubmissionService getInProgressSubmissionService(InProgressSubm public DSpaceObjectService getDSpaceObjectService(T dso) { // No need to worry when supressing, as long as our "getDSpaceObjectManager" method is properly implemented // no casting issues should occur - @SuppressWarnings("unchecked") DSpaceObjectService manager = getDSpaceObjectService(dso.getType()); return manager; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java index 870a95b7f766..81c867f92120 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java @@ -9,8 +9,8 @@ import java.sql.SQLException; import java.util.List; +import java.util.Objects; -import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.dao.SubscriptionParameterDAO; import org.dspace.eperson.service.SubscriptionParameterService; @@ -34,8 +34,8 @@ public List findAll(Context context) throws SQLException } @Override - public SubscriptionParameter add(Context context, String name, String value, - Subscription subscription) throws SQLException, AuthorizeException { + public SubscriptionParameter add(Context context, String name, String value, Subscription subscription) + throws SQLException { SubscriptionParameter subscriptionParameter = subscriptionParameterDAO.create(context, new SubscriptionParameter()); subscriptionParameter.setName(name); @@ -43,12 +43,12 @@ public SubscriptionParameter add(Context context, String name, String value, subscriptionParameter.setValue(value); return subscriptionParameter; } + @Override - public SubscriptionParameter edit(Context context,Integer id,String value, - String name, Subscription subscription) throws SQLException, AuthorizeException { + public SubscriptionParameter edit(Context context, Integer id, String value, String name, Subscription subscription) + throws SQLException { SubscriptionParameter subscriptionParameter = - subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); - subscriptionParameter.setId(id); + subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); subscriptionParameter.setName(name); subscriptionParameter.setSubscription(subscription); subscriptionParameter.setValue(value); @@ -62,16 +62,15 @@ public SubscriptionParameter findById(Context context, int id) throws SQLExcepti } @Override - public void deleteSubscriptionParameter(Context context, Integer id) throws SQLException, AuthorizeException { + public void delete(Context context, Integer id) throws SQLException { SubscriptionParameter subscriptionParameter = subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); - if (subscriptionParameter != null) { + if (Objects.nonNull(subscriptionParameter)) { subscriptionParameter.setSubscription(null); subscriptionParameterDAO.delete(context, subscriptionParameter); } else { throw new SQLException("Subscription parameter with id" + id + "do not exists"); } - } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 817085cdda04..6ed9ca2d5acb 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Objects; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; @@ -43,9 +44,9 @@ public class SubscribeServiceImpl implements SubscribeService { private CollectionService collectionService; @Override - public List findAll(Context context, String resourceType, - Integer limit, Integer offset) throws Exception { - if (resourceType == null) { + public List findAll(Context context, String resourceType, Integer limit, Integer offset) + throws Exception { + if (StringUtils.isBlank(resourceType)) { return subscriptionDAO.findAllOrderedByDSO(context, limit, offset); } else { if (resourceType.equals("Item") || resourceType.equals("Collection") || resourceType.equals("Community")) { @@ -101,24 +102,25 @@ public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObj } @Override - public List getSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) + public List findSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit,Integer offset) throws SQLException { return subscriptionDAO.findByEPerson(context, eperson, limit, offset); } @Override - public List getSubscriptionsByEPersonAndDso(Context context,EPerson eperson,DSpaceObject dSpaceObject, - Integer limit, Integer offset) throws SQLException { + public List findSubscriptionsByEPersonAndDso(Context context, EPerson eperson, + DSpaceObject dSpaceObject, + Integer limit, Integer offset) throws SQLException { return subscriptionDAO.findByEPersonAndDso(context, eperson, dSpaceObject, limit, offset); } @Override - public List getAvailableSubscriptions(Context context) throws SQLException { - return getAvailableSubscriptions(context, null); + public List findAvailableSubscriptions(Context context) throws SQLException { + return findAvailableSubscriptions(context, null); } @Override - public List getAvailableSubscriptions(Context context, EPerson eperson) throws SQLException { + public List findAvailableSubscriptions(Context context, EPerson eperson) throws SQLException { if (Objects.nonNull(eperson)) { context.setCurrentUser(eperson); } @@ -141,91 +143,50 @@ public void deleteByEPerson(Context context, EPerson ePerson) throws SQLExceptio } @Override - public Subscription findById(Context context, int id) throws SQLException, AuthorizeException { - Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); - if (context.getCurrentUser().equals(subscription.getePerson()) || - authorizeService.isAdmin(context, context.getCurrentUser())) { - return subscription; - } - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); + public Subscription findById(Context context, int id) throws SQLException { + return subscriptionDAO.findByID(context, Subscription.class, id); } @Override - public Subscription updateSubscription(Context context, Integer id, - EPerson eperson, - DSpaceObject dSpaceObject, - List subscriptionParameterList, - String type) throws SQLException, AuthorizeException { - // must be admin or the subscriber of the subscription - if (authorizeService.isAdmin(context, context.getCurrentUser()) || eperson.equals(context.getCurrentUser())) { - Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); - subscriptionDB.removeParameterList(); - subscriptionDB.setType(type); - subscriptionDB.setdSpaceObject(dSpaceObject); - subscriptionParameterList.forEach(subscriptionParameter -> - subscriptionDB.addParameter(subscriptionParameter)); - subscriptionDB.setePerson(eperson); - subscriptionDAO.save(context, subscriptionDB); - return subscriptionDB; - } else { - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); - } + public Subscription updateSubscription(Context context, Integer id, EPerson eperson, DSpaceObject dSpaceObject, + List subscriptionParameterList, String type) + throws SQLException { + Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); + subscriptionDB.removeParameterList(); + subscriptionDB.setType(type); + subscriptionDB.setdSpaceObject(dSpaceObject); + subscriptionParameterList.forEach(x -> subscriptionDB.addParameter(x)); + subscriptionDB.setePerson(eperson); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; } @Override - public Subscription addSubscriptionParameter(Context context, Integer id, - SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { - // must be admin or the subscriber of the subscription + public Subscription addSubscriptionParameter(Context context, Integer id, SubscriptionParameter subscriptionParam) + throws SQLException { Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); - if (authorizeService.isAdmin(context, context.getCurrentUser()) - || subscriptionDB.getePerson().equals(context.getCurrentUser())) { - subscriptionDB.addParameter(subscriptionParameter); - subscriptionDAO.save(context, subscriptionDB); - return subscriptionDB; - } else { - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); - } + subscriptionDB.addParameter(subscriptionParam); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; } @Override - public Subscription removeSubscriptionParameter(Context context, Integer id, - SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException { - // must be admin or the subscriber of the subscription + public Subscription removeSubscriptionParameter(Context context,Integer id, SubscriptionParameter subscriptionParam) + throws SQLException { Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); - if (authorizeService.isAdmin(context, context.getCurrentUser()) - || subscriptionDB.getePerson().equals(context.getCurrentUser())) { - subscriptionDB.removeParameter(subscriptionParameter); - subscriptionDAO.save(context, subscriptionDB); - return subscriptionDB; - } else { - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); - } + subscriptionDB.removeParameter(subscriptionParam); + subscriptionDAO.save(context, subscriptionDB); + return subscriptionDB; } @Override - public void deleteSubscription(Context context, Integer id) throws SQLException, AuthorizeException { - // initially find the eperson associated with the subscription - Subscription subscription = subscriptionDAO.findByID(context, Subscription.class, id); - if (subscription != null) { - // must be admin or the subscriber of the subscription - if (authorizeService.isAdmin(context, context.getCurrentUser()) - || subscription.getePerson().equals(context.getCurrentUser())) { - try { - subscriptionDAO.delete(context, subscription); - } catch (SQLException sqlException) { - throw new SQLException(sqlException); - } - } else { - throw new AuthorizeException("Only admin or e-person themselves can delete the subscription"); - } - } else { - throw new IllegalArgumentException("Subscription with id " + id + " is not found"); - } + public void deleteSubscription(Context context, Subscription subscription) throws SQLException { + subscriptionDAO.delete(context, subscription); } @Override - public List findAllSubscriptionsByTypeAndFrequency(Context context, - String type, String frequencyValue) throws SQLException { + public List findAllSubscriptionsByTypeAndFrequency(Context context,String type, String frequencyValue) + throws SQLException { return subscriptionDAO.findAllSubscriptionsByTypeAndFrequency(context, type, frequencyValue); } @@ -235,12 +196,12 @@ public Long countAll(Context context) throws SQLException { } @Override - public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException { + public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException { return subscriptionDAO.countAllByEPerson(context, ePerson); } @Override - public Long countAllByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) + public Long countByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException { return subscriptionDAO.countAllByEPersonAndDso(context, ePerson, dSpaceObject); } diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java index 002b517032b2..590374802cce 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java @@ -17,6 +17,8 @@ import javax.persistence.SequenceGenerator; import javax.persistence.Table; +import org.dspace.core.ReloadableEntity; + /** * Database entity representation of the subscription_parameter table * @@ -24,7 +26,7 @@ */ @Entity @Table(name = "subscription_parameter") -public class SubscriptionParameter { +public class SubscriptionParameter implements ReloadableEntity { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "subscription_parameter_seq") @@ -76,7 +78,8 @@ public void setValue(String value) { this.value = value; } - public Integer getId() { + @Override + public Integer getID() { return id; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index 71518c1fcf0a..4133b09f4777 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -41,8 +41,8 @@ protected SubscriptionDAOImpl() { } @Override - public List findByEPerson(Context context, EPerson eperson, - Integer limit, Integer offset) throws SQLException { + public List findByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) + throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); javax.persistence.criteria.CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index d06c23f1f0a8..98b810ac4905 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -50,8 +50,7 @@ public List findAll(Context context, String resourceType, Integer * @throws AuthorizeException Exception indicating the current user of the context does not have permission * to perform a particular action. */ - public Subscription subscribe(Context context, EPerson eperson, - DSpaceObject dSpaceObject, + public Subscription subscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject, List subscriptionParameterList, String type) throws SQLException, AuthorizeException; @@ -80,7 +79,7 @@ public void unsubscribe(Context context, EPerson eperson, DSpaceObject dSpaceObj * @return array of collections e-person is subscribed to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) + public List findSubscriptionsByEPerson(Context context, EPerson eperson, Integer limit,Integer offset) throws SQLException; /** @@ -94,11 +93,9 @@ public List getSubscriptionsByEPerson(Context context, EPerson epe * @return array of collections e-person is subscribed to and related with dso * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getSubscriptionsByEPersonAndDso(Context context, - EPerson eperson, + public List findSubscriptionsByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject, - Integer limit, - Integer offset) throws SQLException; + Integer limit, Integer offset) throws SQLException; /** * Find out which collections the currently logged in e-person can subscribe to @@ -107,7 +104,7 @@ public List getSubscriptionsByEPersonAndDso(Context context, * @return array of collections the currently logged in e-person can subscribe to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getAvailableSubscriptions(Context context) throws SQLException; + public List findAvailableSubscriptions(Context context) throws SQLException; /** * Find out which collections an e-person can subscribe to @@ -117,7 +114,7 @@ public List getSubscriptionsByEPersonAndDso(Context context, * @return array of collections e-person can subscribe to * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List getAvailableSubscriptions(Context context, EPerson eperson) throws SQLException; + public List findAvailableSubscriptions(Context context, EPerson eperson) throws SQLException; /** * Is that e-person subscribed to that collection? @@ -155,7 +152,7 @@ public List getSubscriptionsByEPersonAndDso(Context context, * @param id the id of subscription to be searched * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription findById(Context context, int id) throws SQLException, AuthorizeException; + public Subscription findById(Context context, int id) throws SQLException; /** * Updates a subscription by id @@ -168,10 +165,8 @@ public List getSubscriptionsByEPersonAndDso(Context context, * @param type String type * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription updateSubscription(Context context, Integer id, EPerson eperson, - DSpaceObject dSpaceObject, - List subscriptionParameterList, - String type) throws SQLException, AuthorizeException; + public Subscription updateSubscription(Context context, Integer id, EPerson eperson, DSpaceObject dSpaceObject, + List subscriptionParameterList, String type) throws SQLException; /** * Adds a parameter to a subscription @@ -182,18 +177,18 @@ public Subscription updateSubscription(Context context, Integer id, EPerson eper * @throws SQLException An exception that provides information on a database access error or other errors. */ public Subscription addSubscriptionParameter(Context context,Integer id,SubscriptionParameter subscriptionParameter) - throws SQLException, AuthorizeException; + throws SQLException; /** * Deletes a parameter from subscription * * @param context DSpace context * @param id Integer id - * @param subscriptionParameter SubscriptionParameter subscriptionParameter - * @throws SQLException An exception that provides information on a database access error or other errors. + * @param subscriptionParam SubscriptionParameter subscriptionParameter + * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription removeSubscriptionParameter(Context context, Integer id, - SubscriptionParameter subscriptionParameter) throws SQLException, AuthorizeException; + public Subscription removeSubscriptionParameter(Context context, Integer id,SubscriptionParameter subscriptionParam) + throws SQLException; /** * Deletes a subscription @@ -202,7 +197,7 @@ public Subscription removeSubscriptionParameter(Context context, Integer id, * @param id Integer id of subscription * @throws SQLException An exception that provides information on a database access error or other errors. */ - public void deleteSubscription(Context context, Integer id) throws SQLException, AuthorizeException; + public void deleteSubscription(Context context, Subscription subscription) throws SQLException; /** * Finds all subscriptions having given type and frequency @@ -220,7 +215,7 @@ public List findAllSubscriptionsByTypeAndFrequency(Context context * * @param context DSpace context */ - public Long countAll(Context context) throws SQLException, AuthorizeException; + public Long countAll(Context context) throws SQLException; /** * Counts all subscriptions by ePerson @@ -228,7 +223,7 @@ public List findAllSubscriptionsByTypeAndFrequency(Context context * @param context DSpace context * @param ePerson EPerson ePerson */ - public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException, AuthorizeException; + public Long countSubscriptionsByEPerson(Context context, EPerson ePerson) throws SQLException; /** * Counts all subscriptions by ePerson and DSO @@ -237,7 +232,6 @@ public List findAllSubscriptionsByTypeAndFrequency(Context context * @param ePerson EPerson ePerson * @param dSpaceObject DSpaceObject dSpaceObject */ - public Long countAllByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) - throws SQLException, AuthorizeException; + public Long countByEPersonAndDSO(Context context, EPerson ePerson, DSpaceObject dSpaceObject) throws SQLException; } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java index 88052ac20a43..ff6f2b447d49 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java @@ -44,9 +44,8 @@ public interface SubscriptionParameterService { * @throws AuthorizeException Exception indicating the current user of the context does not have permission * to perform a particular action. */ - public SubscriptionParameter add(Context context, String value, - String name, - Subscription subscription) throws SQLException, AuthorizeException; + public SubscriptionParameter add(Context context, String value, String name, Subscription subscription) + throws SQLException; /** * Updates a subscription parameter with id @@ -60,9 +59,8 @@ public SubscriptionParameter add(Context context, String value, * @throws AuthorizeException Exception indicating the current user of the context does not have permission * to perform a particular action. */ - public SubscriptionParameter edit(Context context, Integer id, String value, - String name, - Subscription subscription) throws SQLException, AuthorizeException; + public SubscriptionParameter edit(Context context, Integer id, String value, String name, Subscription subscription) + throws SQLException; /** * Finds a subscriptionParameter by id @@ -73,14 +71,12 @@ public SubscriptionParameter edit(Context context, Integer id, String value, */ public SubscriptionParameter findById(Context context, int id) throws SQLException; - /** * Deletes a subscriptionParameter with id * * @param context DSpace context * @throws SQLException An exception that provides information on a database access error or other errors. */ - public void deleteSubscriptionParameter(Context context, Integer id) throws SQLException, AuthorizeException; - + public void delete(Context context, Integer id) throws SQLException; } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql index ab20e3525730..e94ec3c5049f 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -20,7 +20,7 @@ CREATE TABLE subscription_parameter ( subscription_parameter_id INTEGER NOT NULL, name CHARACTER VARYING(255), - `value` CHARACTER VARYING(255), + value CHARACTER VARYING(255), subscription_id INTEGER NOT NULL, CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 9cc6b7ebea92..89bd528ed402 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -43,7 +43,7 @@ dspace.server.url = http://localhost db.driver = org.h2.Driver db.dialect=org.hibernate.dialect.H2Dialect # Use a 10 second database lock timeout to avoid occasional JDBC lock timeout errors -db.url = jdbc:h2:mem:test;LOCK_TIMEOUT=10000; +db.url = jdbc:h2:mem:test;LOCK_TIMEOUT=10000;NON_KEYWORDS=VALUE db.username = sa db.password = # H2's default schema is PUBLIC diff --git a/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java index 9c1c778d56f2..40e890a8c962 100644 --- a/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/SubscribeBuilder.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.List; +import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -44,18 +45,27 @@ public void cleanup() throws Exception { // Ensure object and any related objects are reloaded before checking to see what needs cleanup subscription = c.reloadEntity(subscription); if (subscription != null) { - delete(subscription); + delete(c, subscription); } c.complete(); indexingService.commit(); } } - @Override - public void delete(Context c, Subscription subscription) throws Exception { - if (subscription != null) { - getService().deleteSubscription(c, subscription.getID()); + public static void deleteSubscription(int id) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Subscription subscription = subscribeService.findById(c, id); + if (Objects.nonNull(subscription)) { + try { + subscribeService.deleteSubscription(c, subscription); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + c.complete(); } + indexingService.commit(); } @Override @@ -71,18 +81,6 @@ public Subscription build() { return subscription; } - public void delete(Subscription subscription) throws Exception { - try (Context c = new Context()) { - c.turnOffAuthorisationSystem(); - Subscription subscription1 = c.reloadEntity(subscription); - if (subscription1 != null) { - getService().deleteSubscription(c, subscription1.getID()); - } - c.complete(); - } - indexingService.commit(); - } - public static SubscribeBuilder subscribeBuilder(final Context context, String type, DSpaceObject dSpaceObject, EPerson ePerson, List subscriptionParameterList) { SubscribeBuilder builder = new SubscribeBuilder(context); @@ -103,4 +101,11 @@ private SubscribeBuilder create(Context context, String type, DSpaceObject dSpac return this; } + @Override + public void delete(Context c, Subscription dso) throws Exception { + if (Objects.nonNull(dso)) { + getService().deleteSubscription(c, dso); + } + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java index bc89116c261e..5c7aeaef4273 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java @@ -41,7 +41,7 @@ public SubscriptionRest convert(Subscription subscription, Projection projection SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); subscriptionParameterRest.setName(subscriptionParameter.getName()); subscriptionParameterRest.setValue(subscriptionParameter.getValue()); - subscriptionParameterRest.setId(subscriptionParameter.getId()); + subscriptionParameterRest.setId(subscriptionParameter.getID()); subscriptionParameterRestList.add(subscriptionParameterRest); } rest.setSubscriptionParameterList(subscriptionParameterRestList); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java index 71e3c47fb73b..7d7c5f8c5809 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java @@ -5,19 +5,20 @@ * * http://www.dspace.org/license/ */ - package org.dspace.app.rest.model; - import org.dspace.eperson.Subscription; +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ public class SubscriptionParameterRest { + private Integer id; private String name; private String value; - public SubscriptionParameterRest() { - } + public SubscriptionParameterRest() {} public SubscriptionParameterRest(Integer id, String name, String value, Subscription subscription) { this.id = id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index 77215cdd50f5..17fad1b0ea8d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -56,8 +56,6 @@ public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, } } catch (SQLException e) { throw new RuntimeException(e); - } catch (AuthorizeException e) { - throw new AuthorizeException(e.getMessage()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index 4a7181f0da2d..738bd1f6c2a5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -43,8 +43,6 @@ public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subs return converter.toRest(subscription.getePerson(), projection); } catch (SQLException e) { throw new RuntimeException(e); - } catch (AuthorizeException e) { - throw new AuthorizeException(e.getMessage()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index e40caff8cd03..2303fde7cf8f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest.repository; +import static org.dspace.app.rest.model.SubscriptionRest.CATEGORY; +import static org.dspace.app.rest.model.SubscriptionRest.NAME; + import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; @@ -19,14 +22,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.model.patch.Patch; -import org.dspace.app.rest.repository.patch.ResourcePatch; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -48,39 +51,32 @@ /** * This is the repository responsible to manage SubscriptionRest object * - * @author Alba Aliu at atis.al + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME) -public class SubscriptionRestRepository extends DSpaceRestRepository - implements LinkRestRepository { +public class SubscriptionRestRepository extends DSpaceRestRepository { @Autowired private ConverterService converter; @Autowired - private EPersonService personService; + private EPersonService ePersonService; @Autowired private AuthorizeService authorizeService; @Autowired private SubscribeService subscribeService; @Autowired private DSpaceObjectUtils dspaceObjectUtil; - @Autowired - private ResourcePatch resourcePatch; @Override - @PreAuthorize("isAuthenticated()") + @PreAuthorize("hasPermission(#id, 'subscription', 'READ')") public SubscriptionRest findOne(Context context, Integer id) { + Subscription subscription = null; try { - Subscription subscription = subscribeService.findById(context, id); - if (Objects.isNull(subscription)) { - throw new ResourceNotFoundException("The subscription for ID: " + id + " could not be found"); - } - return converter.toRest(subscription, utils.obtainProjection()); - } catch (SQLException sqlException) { - throw new RuntimeException(sqlException.getMessage(), sqlException); - } catch (AuthorizeException authorizeException) { - throw new RuntimeException(authorizeException.getMessage()); + subscription = subscribeService.findById(context, id); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); } + return Objects.isNull(subscription) ? null : converter.toRest(subscription, utils.obtainProjection()); } @Override @@ -90,91 +86,77 @@ public Page findAll(Context context, Pageable pageable) { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String resourceType = req.getParameter("resourceType"); List subscriptionList = subscribeService.findAll(context, resourceType, - pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); + Math.toIntExact(pageable.getPageSize()), + Math.toIntExact(pageable.getOffset())); Long total = subscribeService.countAll(context); return converter.toRestPage(subscriptionList, pageable, total, utils.obtainProjection()); } catch (Exception e) { - throw new RuntimeException(e.getMessage()); + throw new RuntimeException(e.getMessage(), e); } } - @PreAuthorize("isAuthenticated()") @SearchRestMethod(name = "findByEPerson") - public Page findAllSubscriptionsByEPerson(String id, Pageable pageable) throws Exception { + @PreAuthorize("hasPermission(#epersonId, 'AdminOrOwner', 'READ')") + public Page findSubscriptionsByEPerson(@Parameter(value = "uuid", required = true) UUID epersonId, + Pageable pageable)throws Exception { + Long total = null; + List subscriptions = null; try { Context context = obtainContext(); - EPerson ePerson = personService.findByIdOrLegacyId(context, id); - if (context.getCurrentUser().equals(ePerson) - || authorizeService.isAdmin(context, context.getCurrentUser())) { - List subscriptionList = subscribeService.getSubscriptionsByEPerson(context, - ePerson, pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); - Long total = subscribeService.countAllByEPerson(context, ePerson); - return converter.toRestPage(subscriptionList, pageable, total, utils.obtainProjection()); - } else { - throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); - } - } catch (SQLException sqlException) { - throw new SQLException(sqlException.getMessage(), sqlException); - - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); + EPerson ePerson = ePersonService.find(context, epersonId); + subscriptions = subscribeService.findSubscriptionsByEPerson(context, ePerson, + Math.toIntExact(pageable.getPageSize()), + Math.toIntExact(pageable.getOffset())); + total = subscribeService.countSubscriptionsByEPerson(context, ePerson); + } catch (SQLException e) { + throw new SQLException(e.getMessage(), e); } + return converter.toRestPage(subscriptions, pageable, total, utils.obtainProjection()); } - @PreAuthorize("isAuthenticated()") @SearchRestMethod(name = "findByEPersonAndDso") - public Page findByEPersonAndDso(Pageable pageable) throws Exception { + @PreAuthorize("hasPermission(#epersonId, 'AdminOrOwner', 'READ')") + public Page findByEPersonAndDso(@Parameter(value = "eperson_id", required = true) UUID epersonId, + @Parameter(value = "dspace_object_id",required = true) UUID dsoId, + Pageable pageable) throws Exception { + Long total = null; + List subscriptions = null; try { Context context = obtainContext(); - HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); - String epersonId = req.getParameter("eperson_id"); - String dsoId = req.getParameter("dspace_object_id"); - DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); - EPerson ePerson = personService.findByIdOrLegacyId(context, epersonId); - // dso must always be set - if (dsoId == null || epersonId == null) { - throw new UnprocessableEntityException("error parsing the body"); - } - if (context.getCurrentUser().equals(ePerson) - || authorizeService.isAdmin(context, context.getCurrentUser())) { - List subscriptionList = - subscribeService.getSubscriptionsByEPersonAndDso(context, ePerson, dSpaceObject, - pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); - Long total = subscribeService.countAllByEPersonAndDSO(context, ePerson, dSpaceObject); - return converter.toRestPage(subscriptionList, pageable, total, - utils.obtainProjection()); - } else { - throw new AuthorizeException("Only admin or e-person themselves can search for it's subscription"); - } - } catch (SQLException sqlException) { - throw new SQLException(sqlException.getMessage(), sqlException); - - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); + DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, dsoId); + EPerson ePerson = ePersonService.find(context, epersonId); + subscriptions = subscribeService.findSubscriptionsByEPersonAndDso(context, ePerson, dSpaceObject, + Math.toIntExact(pageable.getPageSize()), + Math.toIntExact(pageable.getOffset())); + total = subscribeService.countByEPersonAndDSO(context, ePerson, dSpaceObject); + } catch (SQLException e) { + throw new SQLException(e.getMessage(), e); } + return converter.toRestPage(subscriptions, pageable, total, utils.obtainProjection()); } @Override - @PreAuthorize("isAuthenticated()") protected SubscriptionRest createAndReturn(Context context) throws SQLException, AuthorizeException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); String dsoId = req.getParameter("dspace_object_id"); - // dso must always be set - if (dsoId == null || epersonId == null) { - throw new UnprocessableEntityException("error parsing the body"); + + if (Objects.isNull(dsoId) || Objects.isNull(epersonId)) { + throw new UnprocessableEntityException("Both eperson than DSpaceObject uuids must be provieded!"); } - ObjectMapper mapper = new ObjectMapper(); + try { DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); - EPerson ePerson = personService.findByIdOrLegacyId(context, epersonId); - if (ePerson == null || dSpaceObject == null) { + EPerson ePerson = ePersonService.findByIdOrLegacyId(context, epersonId); + if (Objects.isNull(ePerson) || Objects.isNull(dSpaceObject)) { throw new BadRequestException("Id of person or dspace object must represents reals ids"); } + // user must have read permissions to dataspace object if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, Constants.READ, true)) { throw new AuthorizeException("The user has not READ rights on this DSO"); } + // if user is admin do not make this control, // otherwise make this control because normal user can only subscribe with their own ID of user. if (!authorizeService.isAdmin(context)) { @@ -183,7 +165,7 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, } } ServletInputStream input = req.getInputStream(); - SubscriptionRest subscriptionRest = mapper.readValue(input, SubscriptionRest.class); + SubscriptionRest subscriptionRest = new ObjectMapper().readValue(input, SubscriptionRest.class); Subscription subscription = null; List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); if (subscriptionParameterList != null) { @@ -212,91 +194,71 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, } @Override - @PreAuthorize("isAuthenticated()") + @PreAuthorize("hasPermission(#id, 'subscription', 'WRITE')") protected SubscriptionRest put(Context context, HttpServletRequest request, String apiCategory, String model, Integer id, JsonNode jsonNode) throws SQLException, AuthorizeException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); String dsoId = req.getParameter("dspace_object_id"); - SubscriptionRest subscriptionRest = null; - DSpaceObject dSpaceObject = null; - EPerson ePerson = null; + + SubscriptionRest subscriptionRest; try { subscriptionRest = new ObjectMapper().readValue(jsonNode.toString(), SubscriptionRest.class); } catch (IOException e) { - throw new UnprocessableEntityException("Error parsing subscription json: " + e.getMessage()); + throw new UnprocessableEntityException("Error parsing subscription json: " + e.getMessage(), e); } - String notFoundException = "ResourceNotFoundException:" + apiCategory + "." + model - + " with id: " + id + " not found"; - Subscription subscription; - try { - subscription = subscribeService.findById(context, id); - if (subscription == null) { - throw new ResourceNotFoundException(notFoundException); - } - dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); - ePerson = personService.findByIdOrLegacyId(context, epersonId); - if (dSpaceObject == null || ePerson == null) { - throw new ResourceNotFoundException(notFoundException); - } - } catch (SQLException e) { - throw new ResourceNotFoundException(notFoundException); - } catch (AuthorizeException e) { - throw new AuthorizeException(e.getMessage()); + + Subscription subscription = subscribeService.findById(context, id); + if (Objects.isNull(subscription)) { + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); } + + EPerson ePerson = ePersonService.findByIdOrLegacyId(context, epersonId); + if (Objects.isNull(ePerson)) { + throw new ResourceNotFoundException("There is not ePerson with uuid:" + epersonId); + } + DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); + if (Objects.isNull(dSpaceObject)) { + throw new ResourceNotFoundException("There is not DSpaceObject with uuid:" + dsoId); + } + if (id.equals(subscription.getID())) { List subscriptionParameterList = new ArrayList<>(); - for (SubscriptionParameterRest subscriptionParameterRest : - subscriptionRest.getSubscriptionParameterList()) { + for (SubscriptionParameterRest subscriptionParamRest : subscriptionRest.getSubscriptionParameterList()) { SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setSubscription(subscription); - subscriptionParameter.setValue(subscriptionParameterRest.getValue()); - subscriptionParameter.setName(subscriptionParameterRest.getName()); + subscriptionParameter.setValue(subscriptionParamRest.getValue()); + subscriptionParameter.setName(subscriptionParamRest.getName()); subscriptionParameterList.add(subscriptionParameter); } - subscription = subscribeService.updateSubscription(context, id, ePerson, - dSpaceObject, subscriptionParameterList, subscriptionRest.getSubscriptionType()); + subscription = subscribeService.updateSubscription(context, id, ePerson, dSpaceObject, + subscriptionParameterList, subscriptionRest.getSubscriptionType()); context.commit(); return converter.toRest(subscription, utils.obtainProjection()); } else { - throw new IllegalArgumentException("The id in the Json and the id in the url do not match: " - + id + ", " - + subscription.getID()); + throw new IllegalArgumentException("The id in the Json and the id in the url do not match: " + id + ", " + + subscription.getID()); } } @Override - @PreAuthorize("isAuthenticated()") - public void patch(Context context,HttpServletRequest request,String category, String model, Integer id, Patch patch) - throws UnprocessableEntityException, DSpaceBadRequestException, AuthorizeException { + @PreAuthorize("hasPermission(#id, 'subscription', 'DELETE')") + protected void delete(Context context, Integer id) throws AuthorizeException { try { Subscription subscription = subscribeService.findById(context, id); - if (subscription == null) { - throw new ResourceNotFoundException(category + "." + model + " with id: " + id + " not found"); - } - if (!authorizeService.isAdmin(context) || subscription.getePerson().equals(context.getCurrentUser())) { - throw new AuthorizeException("Only admin or e-person themselves can edit the subscription"); + if (Objects.isNull(subscription)) { + throw new ResourceNotFoundException(CATEGORY + "." + NAME + " with id: " + id + " not found"); } - resourcePatch.patch(context, subscription, patch.getOperations()); + subscribeService.deleteSubscription(context, subscription); } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); - } catch (RuntimeException runtimeException) { - throw new RuntimeException(runtimeException.getMessage()); + throw new RuntimeException("Unable to delete Subscription with id = " + id, e); } } @Override - @PreAuthorize("isAuthenticated()") - public void delete(Context context, Integer id) throws AuthorizeException { - try { - subscribeService.deleteSubscription(context, id); - } catch (SQLException sqlException) { - throw new RuntimeException(sqlException.getMessage(), sqlException); - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); - } + protected void patch(Context c, HttpServletRequest req, String category, String model, Integer id, Patch patch) { + throw new RepositoryMethodNotImplementedException(SubscriptionRest.NAME, "patch"); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java deleted file mode 100644 index 2393d49f2306..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterAddOperation.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.repository.patch.operation; - -import java.sql.SQLException; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.SubscriptionParameterRest; -import org.dspace.app.rest.model.patch.JsonValueEvaluator; -import org.dspace.app.rest.model.patch.Operation; -import org.dspace.core.Context; -import org.dspace.eperson.Subscription; -import org.dspace.eperson.SubscriptionParameter; -import org.dspace.eperson.service.SubscribeService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - - - -/** - * Implementation for SubscriptionParameterAddOperation patches. - *

- * Example: - * curl -X PATCH http://${dspace.server.url}api/core/subscriptions/<:id-subscription> -H " - * Content-Type: application/json" -d '[{ "op": "replace", "path": " - * - */ -@Component -public class SubscriptionParameterAddOperation extends PatchOperation { - - @Autowired - private SubscribeService subscribeService; - - - @Override - public Subscription perform(Context context, Subscription subscription, Operation operation) - throws SQLException { - if (supports(subscription, operation)) { - JsonNode value = null; - ObjectMapper objectMapper = new ObjectMapper(); - try { - if (operation.getValue() instanceof JsonValueEvaluator) { - value = ((JsonValueEvaluator) operation.getValue()).getValueNode(); - } else { - value = objectMapper.readTree((String) operation.getValue()); - } - SubscriptionParameterRest subscriptionParameterRest = - objectMapper.readValue(value.toString(), SubscriptionParameterRest.class); - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setSubscription(subscription); - subscriptionParameter.setValue(subscriptionParameterRest.getValue()); - subscriptionParameter.setName(subscriptionParameterRest.getName()); - subscribeService.addSubscriptionParameter(context, subscription.getID(), subscriptionParameter); - } catch (UnprocessableEntityException e) { - throw new UnprocessableEntityException(e.getMessage(), e); - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } - } else { - throw new DSpaceBadRequestException("Subscription does not support this operation"); - } - return subscription; - } - - @Override - public boolean supports(Object objectToMatch, Operation operation) { - return (objectToMatch instanceof Subscription - && operation.getOp().trim().equalsIgnoreCase(OPERATION_ADD)); - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java deleted file mode 100644 index 660ed7f8d4f1..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterRemoveOperation.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.repository.patch.operation; - -import java.sql.SQLException; - -import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.exception.RESTAuthorizationException; -import org.dspace.app.rest.model.patch.Operation; -import org.dspace.authorize.AuthorizeException; -import org.dspace.core.Context; -import org.dspace.eperson.Subscription; -import org.dspace.eperson.SubscriptionParameter; -import org.dspace.eperson.service.SubscribeService; -import org.dspace.eperson.service.SubscriptionParameterService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * Implementation for SubscriptionParameterRemoveOperation patches. - *

- * Example: - * curl -X PATCH http://${dspace.server.url}api/core/subscriptions/<:id-subscription> -H " - * Content-Type: application/json" -d '[{ "op": "replace", "path": " - * - */ -@Component -public class SubscriptionParameterRemoveOperation extends PatchOperation { - - @Autowired - private SubscriptionParameterService subscriptionParameterService; - @Autowired - private SubscribeService subscribeService; - - - @Override - public Subscription perform(Context context, Subscription subscription, Operation operation) - throws SQLException { - if (supports(subscription, operation)) { - Integer path = Integer.parseInt(operation.getPath().split("/")[2]); - try { - SubscriptionParameter subscriptionParameter = subscriptionParameterService.findById(context, path); - subscribeService.removeSubscriptionParameter(context, subscription.getID(), subscriptionParameter); - } catch (AuthorizeException e) { - throw new RESTAuthorizationException("Unauthorized user for removing subscription parameter"); - } - } else { - throw new DSpaceBadRequestException("Subscription does not support this operation"); - - } - return subscription; - } - - @Override - public boolean supports(Object objectToMatch, Operation operation) { - return (objectToMatch instanceof Subscription - && operation.getOp().trim().equalsIgnoreCase(OPERATION_REMOVE)); - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java deleted file mode 100644 index c9149aa115bf..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/SubscriptionParameterReplaceOperation.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.repository.patch.operation; - -import java.sql.SQLException; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.exception.DSpaceBadRequestException; -import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.SubscriptionParameterRest; -import org.dspace.app.rest.model.patch.JsonValueEvaluator; -import org.dspace.app.rest.model.patch.Operation; -import org.dspace.core.Context; -import org.dspace.eperson.Subscription; -import org.dspace.eperson.service.SubscriptionParameterService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * Implementation for SubscriptionParameterReplaceOperation patches. - *

- * Example: - * curl -X PATCH http://${dspace.server.url}api/core/subscriptions/<:id-subscription> -H " - * Content-Type: application/json" -d '[{ "op": "replace", "path": " - * - */ -@Component -public class SubscriptionParameterReplaceOperation extends PatchOperation { - - @Autowired - private SubscriptionParameterService subscriptionParameterService; - - @Override - public Subscription perform(Context context, Subscription subscription, Operation operation) - throws SQLException { - if (supports(subscription, operation)) { - Integer subscriptionParameterId = Integer.parseInt(operation.getPath().split("/", 3)[2]); - checkModelForExistingValue(subscription, subscriptionParameterId); - JsonNode value = null; - ObjectMapper objectMapper = new ObjectMapper(); - try { - if (operation.getValue() instanceof JsonValueEvaluator) { - value = ((JsonValueEvaluator) operation.getValue()).getValueNode(); - } else { - value = objectMapper.readTree((String) operation.getValue()); - } - SubscriptionParameterRest subscriptionParameterRest = objectMapper.readValue( - value.toString(), SubscriptionParameterRest.class); - subscriptionParameterService.edit(context, subscriptionParameterId,subscriptionParameterRest.getValue(), - subscriptionParameterRest.getName(), subscription); - } catch (UnprocessableEntityException e) { - throw new UnprocessableEntityException(e.getMessage(), e); - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } - return subscription; - } else { - throw new DSpaceBadRequestException("Subscription does not support this operation"); - } - } - - @Override - public boolean supports(Object objectToMatch, Operation operation) { - return (objectToMatch instanceof Subscription && operation.getOp().trim().equalsIgnoreCase(OPERATION_REPLACE)); - } - - /** - * Checks whether the subscription - */ - @SuppressWarnings("ReturnValueIgnored") - private void checkModelForExistingValue(Subscription subscription, Integer id) { - subscription.getSubscriptionParameterList().stream().filter(subscriptionParameter -> { - return subscriptionParameter.getId().equals(id); - }).findFirst().orElseThrow(); - } - -} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.java new file mode 100644 index 000000000000..7e58afd60107 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.java @@ -0,0 +1,73 @@ +/** + * 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.sql.SQLException; +import java.util.Objects; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +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; + +/** + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ +@Component +public class SubscriptionAdminAndOwnerPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.class); + + @Autowired + private RequestService requestService; + @Autowired + private AuthorizeService authorizeService; + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, + String targetType, DSpaceRestPermission permission) { + + DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); + + if (!DSpaceRestPermission.READ.equals(restPermission) && + !DSpaceRestPermission.WRITE.equals(restPermission) && + !DSpaceRestPermission.DELETE.equals(restPermission) || + !StringUtils.equals(targetType, "AdminOrOwner")) { + return false; + } + + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + + UUID dsoId = UUID.fromString(targetId.toString()); + EPerson currentUser = context.getCurrentUser(); + + // anonymous user + if (Objects.isNull(currentUser)) { + return false; + } + + try { + return dsoId.equals(currentUser.getID()) || authorizeService.isAdmin(context, currentUser); + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return false; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java new file mode 100644 index 000000000000..b92fed943534 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java @@ -0,0 +1,84 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security; + +import static org.dspace.app.rest.model.SubscriptionRest.NAME; +import static org.dspace.app.rest.security.DSpaceRestPermission.DELETE; +import static org.dspace.app.rest.security.DSpaceRestPermission.READ; +import static org.dspace.app.rest.security.DSpaceRestPermission.WRITE; + +import java.io.Serializable; +import java.sql.SQLException; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.service.SubscribeService; +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; + +/** + * {@link RestPermissionEvaluatorPlugin} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +@Component +public class SubscriptionRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(SubscriptionRestPermissionEvaluatorPlugin.class); + + @Autowired + private RequestService requestService; + @Autowired + private SubscribeService subscribeService; + @Autowired + private AuthorizeService authorizeService; + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission permission) { + + DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); + + if (!READ.equals(restPermission) && !WRITE.equals(restPermission) && !DELETE.equals(restPermission) + || !StringUtils.equalsIgnoreCase(targetType, NAME)) { + return false; + } + + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); + + try { + EPerson currentUser = context.getCurrentUser(); + // anonymous user + if (Objects.isNull(currentUser)) { + return false; + } + // Admin user + if (authorizeService.isAdmin(context, currentUser)) { + return true; + } + + Subscription subscription = subscribeService.findById(context, Integer.parseInt(targetId.toString())); + return Objects.nonNull(subscription) ? currentUser.equals(subscription.getePerson()) : true; + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return false; + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index dc3519fe0763..15d57b33765a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -7,10 +7,11 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -21,14 +22,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.matcher.SubscriptionMatcher; import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.SubscriptionRest; -import org.dspace.app.rest.model.patch.AddOperation; -import org.dspace.app.rest.model.patch.Operation; -import org.dspace.app.rest.model.patch.RemoveOperation; -import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; @@ -84,39 +83,34 @@ public void setUp() throws Exception { @Test public void findAll() throws Exception { context.turnOffAuthorisationSystem(); - List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TypeTest", publicItem, admin, subscriptionParameterList).build(); - subscriptionParameter.setSubscription(subscription); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); + List subscriptionParameterList2 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); + subscriptionParameter2.setName("Frequency"); + subscriptionParameter2.setValue("WEEKLY"); + subscriptionParameterList2.add(subscriptionParameter2); + Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, + "TestType 2", collection, admin, subscriptionParameterList2).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(get("/api/core/subscriptions")) - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) - .andExpect(jsonPath("$._links.self.href", Matchers.is(REST_SERVER_URL + "core/subscriptions"))); + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.containsInAnyOrder( + SubscriptionMatcher.matchSubscription(subscription1), + SubscriptionMatcher.matchSubscription(subscription2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))); } @Test @@ -141,39 +135,26 @@ public void findAllWithResourceType() throws Exception { subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TypeTest", publicItem, admin, subscriptionParameterList).build(); - subscriptionParameter.setSubscription(subscription); + "TypeTest", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(get("/api/core/subscriptions?resourceType=Item")) .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.contains( + SubscriptionMatcher.matchSubscription(subscription) + ))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TypeTest"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", is("Daily"))) - .andExpect(jsonPath("$._links.self.href", - Matchers.is(REST_SERVER_URL + "core/subscriptions?resourceType=Item"))); + .andExpect(jsonPath("$.page.number", is(0))); // search for subscriptions related with collections getClient(tokenAdmin).perform(get("/api/core/subscriptions?resourceType=Collection")) .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) //By default we expect at least 1 submission forms so this to be reflected in the page object .andExpect(jsonPath("$.page.size", is(20))) @@ -185,7 +166,7 @@ public void findAllWithResourceType() throws Exception { } @Test - public void findByIdAsAdministrator() throws Exception { + public void findOneWithOwnerTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); @@ -193,7 +174,39 @@ public void findByIdAsAdministrator() throws Exception { subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "TestType", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //By default we expect at least 1 submission forms so this to be reflected in the page object + .andExpect(jsonPath("$.subscriptionType", is("TestType"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) + .andExpect(jsonPath("$._links.self.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) + .andExpect(jsonPath("$._links.dSpaceObject.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) + .andExpect(jsonPath("$._links.ePerson.href", + Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))); + } + + @Test + public void findOneAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -216,7 +229,7 @@ public void findByIdAsAdministrator() throws Exception { } @Test - public void findByIdAsAnonymous() throws Exception { + public void findOneAnonymousTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); @@ -224,7 +237,7 @@ public void findByIdAsAnonymous() throws Exception { subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/subscriptions/" + subscription.getID())) @@ -232,8 +245,7 @@ public void findByIdAsAnonymous() throws Exception { } @Test - //TODO - public void findByIdNotAsSubscriberNotAsAdmin() throws Exception { + public void findOneForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); @@ -241,90 +253,148 @@ public void findByIdNotAsSubscriberNotAsAdmin() throws Exception { subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID())) - .andExpect(status().isInternalServerError()); + .andExpect(status().isForbidden()); + } + + @Test + public void findOneNotFoundTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE)) + .andExpect(status().isNotFound()); } @Test - public void findAllSubscriptionsByEPerson() throws Exception { + public void findSubscriptionsByEPersonAdminTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson user = EPersonBuilder.createEPerson(context) - .withEmail("user@test.it") - .withPassword(password) - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter1"); - subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameter.setName("Parameter 1"); + subscriptionParameter.setValue("ValueParameter 1"); subscriptionParameterList.add(subscriptionParameter); - SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, user, subscriptionParameterList).build(); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); + List subscriptionParameterList2 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); + subscriptionParameter2.setName("Parameter 2"); + subscriptionParameter2.setValue("ValueParameter 2"); + subscriptionParameterList2.add(subscriptionParameter2); + Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, + "TestType 2", collection, eperson, subscriptionParameterList2).build(); + context.restoreAuthSystemState(); - String tokenEPerson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEPerson).perform(get("/api/core/subscriptions/search/findByEPerson?id=" + eperson.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) - .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionType", is("TestType"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.dSpaceObject.href", - Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._embedded.subscriptions[0]._links.ePerson.href", Matchers.endsWith("ePerson"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].name", is("Parameter1"))) - .andExpect(jsonPath("$._embedded.subscriptions[0].subscriptionParameterList[0].value", - is("ValueParameter1"))); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPerson") + .param("uuid", eperson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.containsInAnyOrder( + SubscriptionMatcher.matchSubscription(subscription1), + SubscriptionMatcher.matchSubscription(subscription2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + @Test + public void findSubscriptionsByEPersonOwnerTest() throws Exception { context.turnOffAuthorisationSystem(); - EPersonBuilder.createEPerson(context) - .withEmail("epersonIT@example.com") - .withPassword(password) - .withLanguage("al") - .build(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter 1"); + subscriptionParameter.setValue("ValueParameter 1"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); + List subscriptionParameterList2 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); + subscriptionParameter2.setName("Parameter 2"); + subscriptionParameter2.setValue("ValueParameter 2"); + subscriptionParameterList2.add(subscriptionParameter2); + Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, + "TestType 2", collection, eperson, subscriptionParameterList2).build(); - subscriptionParameter.setName("Parameter1"); - subscriptionParameter.setValue("ValueParameter1"); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(eperson.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPerson") + .param("uuid", eperson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.containsInAnyOrder( + SubscriptionMatcher.matchSubscription(subscription1), + SubscriptionMatcher.matchSubscription(subscription2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", greaterThanOrEqualTo(2))) + .andExpect(jsonPath("$.page.totalPages", greaterThanOrEqualTo(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findSubscriptionsByEPersonUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter 1"); + subscriptionParameter.setValue("ValueParameter 1"); subscriptionParameterList.add(subscriptionParameter); - List subscriptionParameterList1 = new ArrayList<>(); - SubscriptionParameter subscriptionParameter1 = new SubscriptionParameter(); - subscriptionParameter1.setName("Parameter1"); - subscriptionParameter1.setValue("ValueParameter1"); - subscriptionParameterList1.add(subscriptionParameter1); - SubscribeBuilder.subscribeBuilder(context, "TestType", collection, user, subscriptionParameterList).build(); - SubscribeBuilder.subscribeBuilder(context, "Test", collection, user, subscriptionParameterList1).build(); + SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") - .param("dspace_object_id", collection.getID().toString()) - .param("eperson_id", user.getID().toString())) - .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/core/subscriptions/search/findByEPerson") + .param("uuid", eperson.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findSubscriptionsByEPersonForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson user = EPersonBuilder.createEPerson(context) + .withEmail("user1@mail.com") + .withPassword(password) + .build(); + + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter 1"); + subscriptionParameter.setValue("ValueParameter 1"); + subscriptionParameterList.add(subscriptionParameter); + SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, user, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/search/findByEPerson") + .param("uuid", user.getID().toString())) + .andExpect(status().isForbidden()); } @Test - public void addSubscriptionNotLoggedIn() throws Exception { + public void createSubscriptionUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); + SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); - subscriptionParameterRest.setValue("nameTest"); - subscriptionParameterRest.setName("valueTest"); + subscriptionParameterRest.setValue("Frequency"); + subscriptionParameterRest.setName("Daily"); List subscriptionParameterRestList = new ArrayList<>(); subscriptionParameterRestList.add(subscriptionParameterRest); + SubscriptionRest subscriptionRest = new SubscriptionRest(); subscriptionRest.setType("testType"); + MultiValueMap params = new LinkedMultiValueMap(); params.add("dspace_object_id", publicItem.getID().toString()); params.add("eperson_id", eperson.getID().toString()); + context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); + getClient().perform(post("/api/core/subscriptions") .param("dspace_object_id", publicItem.getID().toString()) .param("eperson_id", eperson.getID().toString()) @@ -334,114 +404,108 @@ public void addSubscriptionNotLoggedIn() throws Exception { } @Test - public void addSubscriptionAsAdmin() throws Exception { - SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); - subscriptionParameterRest.setValue("nameTest"); - subscriptionParameterRest.setName("valueTest"); - List subscriptionParameterRestList = new ArrayList<>(); - subscriptionParameterRestList.add(subscriptionParameterRest); - SubscriptionRest subscriptionRest = new SubscriptionRest(); - subscriptionRest.setType("testType"); - ObjectMapper objectMapper = new ObjectMapper(); + public void createSubscriptionAdminForOtherPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + Map map = new HashMap<>(); - map.put("type", "test"); + map.put("type", "testType"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "frequency"); + sub_list.put("name", "Frequency"); sub_list.put("value", "daily"); list.add(sub_list); map.put("subscriptionParameterList", list); - String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(post("/api/core/subscriptions") - .param("dspace_object_id", publicItem.getID().toString()) - .param("eperson_id", admin.getID().toString()) - .content(objectMapper.writeValueAsString(map)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.subscriptionType", is("testType"))) - .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "core/dSpaceObject"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("nameTest"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("valueTest"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("ePerson"))); + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + try { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.subscriptionType", is("testType"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("ePerson"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + SubscribeBuilder.deleteSubscription(idRef.get()); + } } @Test - public void editSubscriptionAnonymous() throws Exception { + public void createSubscriptionByEPersonTest() throws Exception { context.turnOffAuthorisationSystem(); - List subscriptionParameterList = new ArrayList<>(); - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter1"); - subscriptionParameter.setValue("ValueParameter1"); - subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); - ObjectMapper objectMapper = new ObjectMapper(); - Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); + + Map map = new HashMap<>(); + map.put("type", "testType"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "frequency"); + sub_list.put("name", "Frequency"); sub_list.put("value", "daily"); list.add(sub_list); - newSubscription.put("subscriptionParameterList", list); + map.put("subscriptionParameterList", list); + context.restoreAuthSystemState(); - getClient().perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("dspace_object_id", publicItem.getID().toString()) - .param("eperson_id", admin.getID().toString()) - .content(objectMapper.writeValueAsString(newSubscription)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isUnauthorized()); + AtomicReference idRef = new AtomicReference(); + + try { + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.subscriptionType", is("testType"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("ePerson"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + SubscribeBuilder.deleteSubscription(idRef.get()); + } } @Test - //TODO - public void editSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { + public void createSubscriptionPersonForAnotherPersonTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson epersonIT = EPersonBuilder.createEPerson(context) - .withEmail("epersonIT@example.com") - .withPassword(password) - .withLanguage("al") - .build(); - List subscriptionParameterList = new ArrayList<>(); - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter1"); - subscriptionParameter.setValue("ValueParameter1"); - subscriptionParameterList.add(subscriptionParameter); - Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); - context.restoreAuthSystemState(); - ObjectMapper objectMapper = new ObjectMapper(); - Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); + + EPerson user = EPersonBuilder.createEPerson(context) + .withEmail("user1@mail.com") + .withPassword(password) + .build(); + + Map map = new HashMap<>(); + map.put("type", "testType"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "frequency"); - sub_list.put("value", "daily"); + sub_list.put("name", "Frequency"); + sub_list.put("value", "Daily"); list.add(sub_list); - newSubscription.put("subscriptionParameterList", list); + map.put("subscriptionParameterList", list); - String token = getAuthToken(epersonIT.getEmail(), password); - getClient(token).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("dspace_object_id", publicItem.getID().toString()) - .param("eperson_id", admin.getID().toString()) - .content(objectMapper.writeValueAsString(newSubscription)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - //The status has to be 500 Error - .andExpect(status().isInternalServerError()); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", user.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isForbidden()); } @Test - public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { + public void deleteSubscriptionUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); @@ -449,224 +513,187 @@ public void editSubscriptionAsAdministratorOrSubscriber() throws Exception { subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "Test", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - ObjectMapper objectMapper = new ObjectMapper(); - Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); - List> list = new ArrayList<>(); - Map sub_list = new HashMap<>(); - sub_list.put("name", "frequency"); - sub_list.put("value", "daily"); - list.add(sub_list); - newSubscription.put("subscriptionParameterList", list); - String tokenSubscriber = getAuthToken(eperson.getEmail(), password); - getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("dspace_object_id", publicItem.getID().toString()) - .param("eperson_id", eperson.getID().toString()) - .content(objectMapper.writeValueAsString(newSubscription)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.subscriptionType", is("test"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) - .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); + getClient().perform(delete("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isUnauthorized()); } @Test - public void deleteSubscriptionNotAsSubscriberNotAsAdmin() throws Exception { + public void deleteSubscriptionAdminTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson epersonIT = EPersonBuilder.createEPerson(context) - .withEmail("epersonIT@example.com") - .withPassword(password) - .withLanguage("al") - .build(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "Test", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); - getClient(epersonITtoken).perform(put("/api/core/subscriptions")) - .andExpect(status().isUnauthorized()); + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(delete("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isNoContent()); } @Test - public void deleteSubscriptionAsAdmin() throws Exception { + public void deleteSubscriptionForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); subscriptionParameter.setName("Frequency"); subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); - SubscribeBuilder.subscribeBuilder(context, "Test", publicItem, eperson, subscriptionParameterList).build(); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "Test", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(delete("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteSubscriptionNotFoundTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(put("/api/core/subscriptions")) - .andExpect(status().isOk()); + getClient(tokenAdmin).perform(delete("/api/core/subscriptions/" + Integer.MAX_VALUE)) + .andExpect(status().isNotFound()); } @Test - public void patchReplaceSubscriptionParameterAsAdmin() throws Exception { + public void putSubscriptionUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("TestName"); - subscriptionParameter.setValue("TestValue"); + subscriptionParameter.setName("Parameter1"); + subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "Test", publicItem, eperson, subscriptionParameterList).build(); - List ops = new ArrayList(); - Map value = new HashMap<>(); - value.put("name", "frequency"); - value.put("value", "monthly"); - ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/" - + subscription.getSubscriptionParameterList().get(0).getId(), value); - ops.add(replaceOperation); - String patchBody = getPatchContent(ops); + "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); - String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(patch("/api/core/subscriptions/" + subscription.getID()) - .content(patchBody)) - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.type", is("Test"))) - .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("monthly"))) - .andExpect(jsonPath("$._links.self.href", - Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("type", "test"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "daily"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + getClient().perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", admin.getID().toString()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnauthorized()); } @Test - public void patchSubscriptionParameterNotAsAdminNotAsSubscriber() throws Exception { + public void putSubscriptionForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("TestName"); - subscriptionParameter.setValue("TestValue"); + subscriptionParameter.setName("Parameter1"); + subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "Test", publicItem, eperson, subscriptionParameterList).build(); - List ops = new ArrayList(); - Map value = new HashMap<>(); - value.put("name", "frequency"); - value.put("value", "monthly"); - ReplaceOperation replaceOperation = new ReplaceOperation("/subscriptionsParameter/" - + subscription.getSubscriptionParameterList().get(0).getId(), value); - ops.add(replaceOperation); - String patchBody = getPatchContent(ops); - EPerson epersonIT = EPersonBuilder.createEPerson(context) - .withEmail("epersonIT@example.com") - .withPassword(password) - .withLanguage("al") - .build(); + "TestType", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); - String epersonITtoken = getAuthToken(epersonIT.getEmail(), password); - getClient(epersonITtoken).perform(patch("/api/core/subscriptions/" + subscription.getID()) - .content(patchBody)) - .andExpect(status().isForbidden()); + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("type", "test"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "Frequency"); + sub_list.put("value", "Daily"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("eperson_id", admin.getID().toString()) + .param("dspace_object_id", publicItem.getID().toString()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isForbidden()); } @Test - public void patchAddSubscriptionParameter() throws Exception { + public void putSubscriptionTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("TestName"); - subscriptionParameter.setValue("TestValue"); + subscriptionParameter.setName("Frequency"); + subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "Test", publicItem, eperson, subscriptionParameterList).build(); - String token = getAuthToken(admin.getEmail(), password); - List ops = new ArrayList(); - Map value = new HashMap<>(); - value.put("name", "frequency"); - value.put("value", "monthly"); - AddOperation addOperation = new AddOperation("/subscriptionsParameter/" - + subscription.getSubscriptionParameterList().get(0).getId(), value); - ops.add(addOperation); - String patchBody = getPatchContent(ops); - getClient(token).perform(patch("/api/core/subscriptions/" + subscription.getID()) - .content(patchBody)) - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.type", is("Test"))) - .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("TestName"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("TestValue"))) - .andExpect(jsonPath("$.subscriptionParameterList[1].name", is("frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[1].value", is("monthly"))) - .andExpect(jsonPath("$._links.self.href", - Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", - Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", - Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith(REST_SERVER_URL + "/api/core/ePerson"))); + "TestType", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("type", "test"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "Frequency"); + sub_list.put("value", "WEEKLY"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.subscriptionType", is("test"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("WEEKLY"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))) + .andExpect(jsonPath("$._links.dSpaceObject.href",Matchers.endsWith("/dSpaceObject"))); } @Test - public void patchRemoveSubscriptionParameter() throws Exception { + public void putSubscriptionAdminTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("TestName"); - subscriptionParameter.setValue("TestValue"); + subscriptionParameter.setName("Frequency"); + subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "Test", publicItem, eperson, subscriptionParameterList).build(); - List ops = new ArrayList(); - Map value = new HashMap<>(); - value.put("name", "frequency"); - value.put("value", "monthly"); - RemoveOperation removeOperation = new RemoveOperation("/subscriptionsParameter/" - + subscription.getSubscriptionParameterList().get(0).getId()); - ops.add(removeOperation); - String patchBody = getPatchContent(ops); + "TestType", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(patch("/api/core/subscriptions/" + subscription.getID()) - .content(patchBody)) - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.type", is("Test"))) - .andExpect(jsonPath("$.id", Matchers.endsWith(REST_SERVER_URL + "/api/core/dSpaceObject"))) - .andExpect(jsonPath("$.subscriptionParameterList", Matchers.arrayWithSize(0))) - .andExpect(jsonPath("$._links.self.href", - Matchers.startsWith(REST_SERVER_URL + "/api/core/subscriptions"))); + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("type", "test"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "Frequency"); + sub_list.put("value", "WEEKLY"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.subscriptionType", is("test"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("WEEKLY"))) + .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))) + .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))); } -} +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java new file mode 100644 index 000000000000..e5b49c2d2e72 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java @@ -0,0 +1,57 @@ +package org.dspace.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.test.AbstractControllerIntegrationTest.REST_SERVER_URL; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import java.util.stream.Collectors; + +import org.dspace.eperson.Subscription; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +/** + * Provide convenient org.hamcrest.Matcher to verify a SubscriptionRest json response + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +public class SubscriptionMatcher { + + private SubscriptionMatcher() {} + + public static Matcher matchSubscription(Subscription subscription) { + return allOf( + hasJsonPath("$.id", is(subscription.getID())), + hasJsonPath("$.type", is("subscription")), + hasJsonPath("$.subscriptionType", is(subscription.getType())), + hasJsonPath("$.subscriptionParameterList", Matchers.containsInAnyOrder( + subscription.getSubscriptionParameterList().stream() + .map(x -> SubscriptionMatcher.matchSubscriptionParameter(x.getID(), x.getName(), x.getValue())) + .collect(Collectors.toList()) + )), + //Check links + matchLinks(subscription.getID()) + ); + } + + public static Matcher matchSubscriptionParameter(int id, String name, String value) { + return allOf( + hasJsonPath("$.id", is(id)), + hasJsonPath("$.name", is(name)), + hasJsonPath("$.value", is(value)) + ); + } + + /** + * Gets a matcher for all expected links. + */ + public static Matcher matchLinks(Integer id) { + return HalMatcher.matchLinks(REST_SERVER_URL + "core/subscriptions/" + id, + "dSpaceObject", + "ePerson", + "self" + ); + } + +} \ No newline at end of file From a152b551d45b62a3498936afe3b9515858281d25 Mon Sep 17 00:00:00 2001 From: Joost Date: Wed, 7 Dec 2022 16:32:08 +0100 Subject: [PATCH 108/394] [task 97298] implemented check for allowed domains during registration' --- .../dspace/eperson/AccountServiceImpl.java | 7 +++ .../RegistrationRestRepository.java | 7 +++ .../rest/RegistrationRestRepositoryIT.java | 53 +++++++++++++++++++ .../modules/authentication-password.cfg | 3 ++ dspace/config/modules/rest.cfg | 1 + 5 files changed, 71 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java index 25c61f511a79..9d6a915de9d3 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/AccountServiceImpl.java @@ -15,6 +15,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.core.Email; @@ -52,6 +53,9 @@ public class AccountServiceImpl implements AccountService { @Autowired private ConfigurationService configurationService; + @Autowired + private AuthenticationService authenticationService; + protected AccountServiceImpl() { } @@ -80,6 +84,9 @@ public void sendRegistrationInfo(Context context, String email) if (!configurationService.getBooleanProperty("user.registration", true)) { throw new IllegalStateException("The user.registration parameter was set to false"); } + if (!authenticationService.canSelfRegister(context, null, email)) { + throw new IllegalStateException("self registration is not allowed with this email address"); + } sendInfo(context, email, true, true); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java index a158fdfd8ca4..2d90a3a93902 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -23,6 +23,7 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.RegistrationRest; import org.dspace.app.util.AuthorizeUtil; +import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -52,6 +53,9 @@ public class RegistrationRestRepository extends DSpaceRestRepository registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + try { + configurationService.setProperty("authentication-password.domain.valid", "test.com"); + RegistrationRest registrationRest = new RegistrationRest(); + String email = "testPerson@test.com"; + registrationRest.setEmail(email); + + ObjectMapper mapper = new ObjectMapper(); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(1, registrationDataList.size()); + assertTrue(StringUtils.equalsIgnoreCase(registrationDataList.get(0).getEmail(), email)); + } finally { + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } + } + + @Test + public void testRegisterDomainNotRegistred() throws Exception { + List registrationDataList = new ArrayList<>(); + try { + configurationService.setProperty("authentication-password.domain.valid", "test.com"); + RegistrationRest registrationRest = new RegistrationRest(); + String email = "testPerson@bladibla.com"; + registrationRest.setEmail(email); + + ObjectMapper mapper = new ObjectMapper(); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + assertEquals(0, registrationDataList.size()); + } finally { + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } + } + @Test public void forgotPasswordTest() throws Exception { configurationService.setProperty("user.registration", false); diff --git a/dspace/config/modules/authentication-password.cfg b/dspace/config/modules/authentication-password.cfg index 07aaeed14da1..deb189d44646 100644 --- a/dspace/config/modules/authentication-password.cfg +++ b/dspace/config/modules/authentication-password.cfg @@ -6,6 +6,9 @@ #---------------------------------------------------------------# # +# self-registration can be disabled completely by setting the user.registration property to false +# user.registration = false + # Only emails ending in the following domains are allowed to self-register # Example - example.com domain : @example.com # Example - MIT domain and all .ac.uk domains: @mit.edu, .ac.uk diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 1b0895810bb3..2cc80e1a1b74 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -33,6 +33,7 @@ rest.projection.specificLevel.maxEmbed = 5 rest.properties.exposed = plugin.named.org.dspace.curate.CurationTask rest.properties.exposed = google.analytics.key rest.properties.exposed = versioning.item.history.include.submitter +rest.properties.exposed = authentication-password.domain.valid #---------------------------------------------------------------# # These configs are used by the deprecated REST (v4-6) module # From bd6334d9b062187a8c621ea25ab9693ede6579a3 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 7 Dec 2022 16:45:55 +0100 Subject: [PATCH 109/394] [CST-7565] minor refactoring --- .../app/rest/model/SubscriptionRest.java | 1 - ...ubscriptionDSpaceObjectLinkRepository.java | 5 ++--- .../SubscriptionEPersonLinkRepository.java | 6 ++--- .../SubscriptionRestRepository.java | 22 +++++-------------- .../app/rest/matcher/SubscriptionMatcher.java | 7 ++++++ 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index 394805a728a2..1856a3d40207 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -5,7 +5,6 @@ * * http://www.dspace.org/license/ */ - package org.dspace.app.rest.model; import java.util.ArrayList; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index 17fad1b0ea8d..e011b8b65a75 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -15,7 +15,6 @@ import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; -import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -39,7 +38,7 @@ public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRe public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, @Nullable Pageable optionalPageable, - Integer subscriptionId, Projection projection) throws AuthorizeException { + Integer subscriptionId, Projection projection) { try { Subscription subscription = subscribeService.findById(obtainContext(), subscriptionId); if (Objects.isNull(subscription)) { @@ -55,7 +54,7 @@ public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, return converter.toRest(initializer.getImplementation(), projection); } } catch (SQLException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index 738bd1f6c2a5..4a9dbe7a8f55 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -15,7 +15,6 @@ import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; -import org.dspace.authorize.AuthorizeException; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.SubscribeService; import org.springframework.beans.factory.annotation.Autowired; @@ -33,8 +32,7 @@ public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestReposit private SubscribeService subscribeService; public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subscriptionId, - @Nullable Pageable optionalPageable, - Projection projection) throws AuthorizeException { + @Nullable Pageable optionalPageable, Projection projection) { try { Subscription subscription = subscribeService.findById(obtainContext(), subscriptionId); if (Objects.isNull(subscription)) { @@ -42,7 +40,7 @@ public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subs } return converter.toRest(subscription.getePerson(), projection); } catch (SQLException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 2303fde7cf8f..826e847246ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -25,11 +25,9 @@ import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.SubscriptionRest; -import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -98,7 +96,7 @@ public Page findAll(Context context, Pageable pageable) { @SearchRestMethod(name = "findByEPerson") @PreAuthorize("hasPermission(#epersonId, 'AdminOrOwner', 'READ')") public Page findSubscriptionsByEPerson(@Parameter(value = "uuid", required = true) UUID epersonId, - Pageable pageable)throws Exception { + Pageable pageable) throws Exception { Long total = null; List subscriptions = null; try { @@ -176,18 +174,13 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, subscriptionParameter.setValue(subscriptionParameterRest.getValue()); subscriptionParameters.add(subscriptionParameter); } - subscription = subscribeService.subscribe(context, ePerson, - dSpaceObject, - subscriptionParameters, - subscriptionRest.getSubscriptionType()); + var type = subscriptionRest.getSubscriptionType(); + subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, type); } context.commit(); return converter.toRest(subscription, utils.obtainProjection()); } catch (SQLException sqlException) { throw new SQLException(sqlException.getMessage(), sqlException); - - } catch (AuthorizeException authorizeException) { - throw new AuthorizeException(authorizeException.getMessage()); } catch (IOException ioException) { throw new UnprocessableEntityException("error parsing the body"); } @@ -196,7 +189,7 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, @Override @PreAuthorize("hasPermission(#id, 'subscription', 'WRITE')") protected SubscriptionRest put(Context context, HttpServletRequest request, String apiCategory, String model, - Integer id, JsonNode jsonNode) throws SQLException, AuthorizeException { + Integer id, JsonNode jsonNode) throws SQLException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); @@ -244,7 +237,7 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri @Override @PreAuthorize("hasPermission(#id, 'subscription', 'DELETE')") - protected void delete(Context context, Integer id) throws AuthorizeException { + protected void delete(Context context, Integer id) { try { Subscription subscription = subscribeService.findById(context, id); if (Objects.isNull(subscription)) { @@ -256,11 +249,6 @@ protected void delete(Context context, Integer id) throws AuthorizeException { } } - @Override - protected void patch(Context c, HttpServletRequest req, String category, String model, Integer id, Patch patch) { - throw new RepositoryMethodNotImplementedException(SubscriptionRest.NAME, "patch"); - } - @Override public Class getDomainClass() { return SubscriptionRest.class; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java index e5b49c2d2e72..ad1df79a8bec 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java @@ -1,3 +1,10 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; From 9901bcfd55425d9d070ad56321c8734ff4fd24cf Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 7 Dec 2022 17:37:30 +0100 Subject: [PATCH 110/394] [CST-7756] minor fix --- .../dspace/app/rest/model/SubscriptionRest.java | 7 +++---- .../SubscriptionDSpaceObjectLinkRepository.java | 7 ++++--- .../SubscriptionEPersonLinkRepository.java | 2 ++ .../repository/SubscriptionRestRepository.java | 15 ++++++++++++++- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index 1856a3d40207..4900b6fd235d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -12,10 +12,9 @@ import org.dspace.app.rest.RestResourceController; -@LinksRest(links = { @LinkRest(name = SubscriptionRest.DSPACE_OBJECT, - method = "getDSpaceObject"), @LinkRest( - name = SubscriptionRest.EPERSON, - method = "getEPerson") +@LinksRest(links = { + @LinkRest(name = SubscriptionRest.DSPACE_OBJECT, method = "getDSpaceObject"), + @LinkRest(name = SubscriptionRest.EPERSON, method = "getEPerson") }) public class SubscriptionRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index e011b8b65a75..762736d5d0c6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -36,9 +37,9 @@ public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRe @Autowired private SubscribeService subscribeService; - public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, - @Nullable Pageable optionalPageable, - Integer subscriptionId, Projection projection) { + @PreAuthorize("hasPermission(#subscriptionId, 'subscription', 'READ')") + public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, Integer subscriptionId, + @Nullable Pageable optionalPageable, Projection projection) { try { Subscription subscription = subscribeService.findById(obtainContext(), subscriptionId); if (Objects.isNull(subscription)) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index 4a9dbe7a8f55..4857b1f2d858 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -31,6 +32,7 @@ public class SubscriptionEPersonLinkRepository extends AbstractDSpaceRestReposit @Autowired private SubscribeService subscribeService; + @PreAuthorize("hasPermission(#subscriptionId, 'subscription', 'READ')") public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subscriptionId, @Nullable Pageable optionalPageable, Projection projection) { try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 826e847246ca..9c9986dfb48c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -22,6 +23,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.ConverterService; @@ -39,10 +41,12 @@ import org.dspace.eperson.SubscriptionParameter; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.SubscribeService; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -52,7 +56,8 @@ * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME) -public class SubscriptionRestRepository extends DSpaceRestRepository { +public class SubscriptionRestRepository extends DSpaceRestRepository + implements InitializingBean { @Autowired private ConverterService converter; @@ -64,6 +69,8 @@ public class SubscriptionRestRepository extends DSpaceRestRepository getDomainClass() { return SubscriptionRest.class; } + @Override + public void afterPropertiesSet() throws Exception { + discoverableEndpointsService.register(this, Arrays.asList(Link.of("/api/" + SubscriptionRest.CATEGORY + + "/" + SubscriptionRest.NAME_PLURAL + "/search", SubscriptionRest.NAME_PLURAL + "-search"))); + } + } \ No newline at end of file From e8dc1c9a58f92a74f578d2d790818843ff6b1178 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Dec 2022 17:09:19 +0000 Subject: [PATCH 111/394] Bump postgresql from 42.4.1 to 42.4.3 Bumps [postgresql](https://github.com/pgjdbc/pgjdbc) from 42.4.1 to 42.4.3. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.4.1...REL42.4.3) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6de2b070b3d6..c66f46c9d7c4 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 5.6.5 5.6.5.Final 6.0.23.Final - 42.4.1 + 42.4.3 8.11.1 3.4.0 From 1252a982406670fac319923ad33bdd4b0c52b10c Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Thu, 8 Dec 2022 10:33:31 +0100 Subject: [PATCH 112/394] 97183 SolrServiceIndexItemEditorsPlugin: fixed logger type --- .../org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java index b10d2d72a332..09308be75920 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexItemEditorsPlugin.java @@ -32,7 +32,7 @@ */ public class SolrServiceIndexItemEditorsPlugin implements SolrServiceIndexPlugin { private static final Logger log = org.apache.logging.log4j.LogManager - .getLogger(SolrServiceIndexCollectionSubmittersPlugin.class); + .getLogger(SolrServiceIndexItemEditorsPlugin.class); @Autowired(required = true) protected AuthorizeService authorizeService; From 02cb5243a484c9019cd97097e12e136af6f3dbbf Mon Sep 17 00:00:00 2001 From: jensroets Date: Tue, 6 Dec 2022 11:06:39 +0100 Subject: [PATCH 113/394] 89597 Metadata import (Improve performance) --- .../org/dspace/app/bulkedit/MetadataImport.java | 13 +++++++------ ...7.2_2022.12.06__index_action_resource_policy.sql | 9 +++++++++ dspace/config/modules/bulkedit.cfg | 7 +++++++ 3 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.12.06__index_action_resource_policy.sql diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 469245908a84..ff56c4d6d279 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -598,18 +598,19 @@ public List runImport(Context c, boolean change, changes.add(whatHasChanged); } - if (change) { - //only clear cache if changes have been made. - c.uncacheEntity(wsItem); - c.uncacheEntity(wfItem); - c.uncacheEntity(item); + if (change && (rowCount % configurationService.getIntProperty("bulkedit.change.commit.count") == 0)) { + c.commit(); + log.info(LogHelper.getHeader(c, "metadata_import_commit", "lineNumber=" + rowCount)); } populateRefAndRowMap(line, item == null ? null : item.getID()); // keep track of current rows processed rowCount++; } + if (change) { + c.commit(); + } - c.setMode(originalMode); + c.setMode(Context.Mode.READ_ONLY); // Return the changes diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.12.06__index_action_resource_policy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.12.06__index_action_resource_policy.sql new file mode 100644 index 000000000000..696e84433dcd --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.12.06__index_action_resource_policy.sql @@ -0,0 +1,9 @@ +-- +-- 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/ +-- + +CREATE INDEX resourcepolicy_action_idx ON resourcepolicy(action_id); diff --git a/dspace/config/modules/bulkedit.cfg b/dspace/config/modules/bulkedit.cfg index 9e6ec5937276..cc13da3f4ebc 100644 --- a/dspace/config/modules/bulkedit.cfg +++ b/dspace/config/modules/bulkedit.cfg @@ -32,3 +32,10 @@ # By default, only 'dspace.agreements.end-user' can be deleted in bulk, as doing so allows # an administrator to force all users to re-review the End User Agreement on their next login. bulkedit.allow-bulk-deletion = dspace.agreements.end-user + +### metadata import script ### +# Set the number after which the changes should be committed while running the script +# After 1000 consecutive records everything starts to slow down because too many things are being loaded into memory +# If we commit these to the database these are cleared out of our memory and we don't lose as much performance +bulkedit.change.commit.count = 100 + From 1a9b1e3e942cfd534a1decd5134db29664001f8f Mon Sep 17 00:00:00 2001 From: jensroets Date: Tue, 6 Dec 2022 11:06:39 +0100 Subject: [PATCH 114/394] 89597 Metadata import (Improve performance): edit comment --- dspace/config/modules/bulkedit.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspace/config/modules/bulkedit.cfg b/dspace/config/modules/bulkedit.cfg index cc13da3f4ebc..6174af53a0f3 100644 --- a/dspace/config/modules/bulkedit.cfg +++ b/dspace/config/modules/bulkedit.cfg @@ -35,7 +35,8 @@ bulkedit.allow-bulk-deletion = dspace.agreements.end-user ### metadata import script ### # Set the number after which the changes should be committed while running the script -# After 1000 consecutive records everything starts to slow down because too many things are being loaded into memory +# After too much consecutive records everything starts to slow down because too many things are being loaded into memory # If we commit these to the database these are cleared out of our memory and we don't lose as much performance +# By default this is set to 100 bulkedit.change.commit.count = 100 From 77be5cdc257f07a605ad657ed675f44b04a6a4a2 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Fri, 2 Dec 2022 15:40:19 +0100 Subject: [PATCH 115/394] Improve performance for metadata imports --- .../org/dspace/handle/HandleServiceImpl.java | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java index c417aa479424..e1fcd1bef77f 100644 --- a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java @@ -211,7 +211,7 @@ public String createHandle(Context context, DSpaceObject dso, @Override public void unbindHandle(Context context, DSpaceObject dso) throws SQLException { - List handles = getInternalHandles(context, dso); + List handles = dso.getHandles(); if (CollectionUtils.isNotEmpty(handles)) { for (Handle handle : handles) { //Only set the "resouce_id" column to null when unbinding a handle. @@ -256,7 +256,7 @@ public DSpaceObject resolveToObject(Context context, String handle) @Override public String findHandle(Context context, DSpaceObject dso) throws SQLException { - List handles = getInternalHandles(context, dso); + List handles = dso.getHandles(); if (CollectionUtils.isEmpty(handles)) { return null; } else { @@ -328,20 +328,6 @@ public void modifyHandleDSpaceObject(Context context, String handle, DSpaceObjec //////////////////////////////////////// // Internal methods //////////////////////////////////////// - - /** - * Return the handle for an Object, or null if the Object has no handle. - * - * @param context DSpace context - * @param dso DSpaceObject for which we require our handles - * @return The handle for object, or null if the object has no handle. - * @throws SQLException If a database error occurs - */ - protected List getInternalHandles(Context context, DSpaceObject dso) - throws SQLException { - return handleDAO.getHandlesByDSpaceObject(context, dso); - } - /** * Find the database row corresponding to handle. * From c705e3c92c3c4baea590f87fff538ab2b5181796 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Fri, 2 Dec 2022 16:13:41 +0100 Subject: [PATCH 116/394] Fixing tests --- .../java/org/dspace/handle/HandleServiceImpl.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java index e1fcd1bef77f..330c891173d3 100644 --- a/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/handle/HandleServiceImpl.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -211,17 +212,17 @@ public String createHandle(Context context, DSpaceObject dso, @Override public void unbindHandle(Context context, DSpaceObject dso) throws SQLException { - List handles = dso.getHandles(); - if (CollectionUtils.isNotEmpty(handles)) { - for (Handle handle : handles) { + Iterator handles = dso.getHandles().iterator(); + if (handles.hasNext()) { + while (handles.hasNext()) { + final Handle handle = handles.next(); + handles.remove(); //Only set the "resouce_id" column to null when unbinding a handle. // We want to keep around the "resource_type_id" value, so that we // can verify during a restore whether the same *type* of resource // is reusing this handle! handle.setDSpaceObject(null); - //Also remove the handle from the DSO list to keep a consistent model - dso.getHandles().remove(handle); handleDAO.save(context, handle); From f2d329dc8883191459392212e00a71eaf825491c Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Fri, 9 Dec 2022 09:24:00 +0100 Subject: [PATCH 117/394] Changing a log info to a handler logInfo call --- .../src/main/java/org/dspace/app/bulkedit/MetadataImport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index ff56c4d6d279..20a72a6ca6f4 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -600,7 +600,7 @@ public List runImport(Context c, boolean change, if (change && (rowCount % configurationService.getIntProperty("bulkedit.change.commit.count") == 0)) { c.commit(); - log.info(LogHelper.getHeader(c, "metadata_import_commit", "lineNumber=" + rowCount)); + handler.logInfo(LogHelper.getHeader(c, "metadata_import_commit", "lineNumber=" + rowCount)); } populateRefAndRowMap(line, item == null ? null : item.getID()); // keep track of current rows processed From ec483fef5b9b856056df4307e314fb18f92738b5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 9 Nov 2022 11:05:09 -0600 Subject: [PATCH 118/394] Update BCEL and add correct scope --- dspace-api/pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 685005160094..b277bf058469 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -806,10 +806,11 @@ test - + org.apache.bcel bcel - 6.4.0 + 6.6.0 + test From b0afb15efa6ad962ff2447fdbaf78c23813758ec Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Mon, 12 Dec 2022 10:46:39 +0100 Subject: [PATCH 119/394] Escape special characters in startsWith filter of browse by title --- dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java index 391ed9077162..cee3ae017e5f 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -17,6 +17,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.apache.solr.client.solrj.util.ClientUtils; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; @@ -206,7 +207,8 @@ private DiscoverResult getSolrResponse() throws BrowseException { query.addFilterQueries("{!field f=" + facetField + "_partial}" + value); } if (StringUtils.isNotBlank(startsWith) && orderField != null) { - query.addFilterQueries("bi_" + orderField + "_sort:" + startsWith + "*"); + query.addFilterQueries( + "bi_" + orderField + "_sort:" + ClientUtils.escapeQueryChars(startsWith) + "*"); } // filter on item to be sure to don't include any other object // indexed in the Discovery Search core From d906d9463b76c19bc626077be86f56246572d896 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Mon, 12 Dec 2022 10:50:19 +0100 Subject: [PATCH 120/394] Add test for filtering browse by title with spaces --- .../app/rest/BrowsesResourceControllerIT.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index baf459408dbd..445613b07c72 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -1111,7 +1111,7 @@ public void testBrowseByItemsStartsWith() throws Exception { //We expect the totalElements to be the 1 item present in the collection .andExpect(jsonPath("$.page.totalElements", is(1))) - //As this is is a small collection, we expect to go-to page 0 + //As this is a small collection, we expect to go-to page 0 .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$._links.self.href", containsString("startsWith=Blade"))) @@ -1121,6 +1121,33 @@ public void testBrowseByItemsStartsWith() throws Exception { "Blade Runner", "1982-06-25") ))); + + //Test filtering with spaces: + //** WHEN ** + //An anonymous user browses the items in the Browse by Title endpoint + //with startsWith set to Blade Runner and scope set to Col 1 + getClient().perform(get("/api/discover/browses/title/items?startsWith=Blade Runner") + .param("scope", col1.getID().toString()) + .param("size", "2")) + + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + //We expect the totalElements to be the 1 item present in the collection + .andExpect(jsonPath("$.page.totalElements", is(1))) + //As this is a small collection, we expect to go-to page 0 + .andExpect(jsonPath("$.page.number", is(0))) + .andExpect(jsonPath("$._links.self.href", containsString("startsWith=Blade Runner"))) + + //Verify that the index jumps to the "Blade Runner" item. + .andExpect(jsonPath("$._embedded.items", + contains(ItemMatcher.matchItemWithTitleAndDateIssued(item2, + "Blade Runner", + "1982-06-25") + ))); } @Test From a9cee40a15cdb00b4bf2e5f96bc7129beb57d36d Mon Sep 17 00:00:00 2001 From: Johanna Staudinger Date: Wed, 16 Nov 2022 14:14:25 +0100 Subject: [PATCH 121/394] DataCite plugin for import Plugin for import of external metadata from DataCite for publications. Code by @johannastaudinger, @floriangantner and @philipprumpf. --- .../datacite/DataCiteFieldMapping.java | 38 ++ ...taCiteImportMetadataSourceServiceImpl.java | 379 ++++++++++++++++++ .../spring-dspace-addon-import-services.xml | 8 + .../config/spring/api/external-services.xml | 13 +- .../rest/ExternalSourcesRestControllerIT.java | 2 +- .../spring/api/datacite-integration.xml | 43 ++ 6 files changed, 481 insertions(+), 2 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java create mode 100644 dspace/config/spring/api/datacite-integration.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java new file mode 100644 index 000000000000..f8540307b916 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteFieldMapping.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.datacite; + +import java.util.Map; +import javax.annotation.Resource; + +import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping; + +/** + * An implementation of {@link AbstractMetadataFieldMapping} + * Responsible for defining the mapping of the datacite metadatum fields on the DSpace metadatum fields + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + */ +public class DataCiteFieldMapping extends AbstractMetadataFieldMapping { + + /** + * Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + * only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + * what metadatafield is generated. + * + * @param metadataFieldMap The map containing the link between retrieve metadata and metadata that will be set to + * the item. + */ + @Override + @Resource(name = "dataciteMetadataFieldMap") + public void setMetadataFieldMap(Map metadataFieldMap) { + super.setMetadataFieldMap(metadataFieldMap); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java new file mode 100644 index 000000000000..b4235f1fe838 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java @@ -0,0 +1,379 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.datacite; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.Callable; +import javax.el.MethodNotFoundException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +import com.google.gson.Gson; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.ReadContext; +import net.minidev.json.JSONArray; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.HttpGet; +import org.dspace.content.Item; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.datamodel.Query; +import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.service.AbstractImportMetadataSourceService; +import org.dspace.importer.external.service.DoiCheck; +import org.dspace.importer.external.service.components.QuerySource; + +/** + * Implements a data source for querying Datacite + * Mainly copied from CrossRefImportMetadataSourceServiceImpl. + * + * optional Affiliation informations are not part of the API request. + * https://support.datacite.org/docs/can-i-see-more-detailed-affiliation-information-in-the-rest-api + * + * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + * + */ +public class DataCiteImportMetadataSourceServiceImpl + extends AbstractImportMetadataSourceService implements QuerySource { + + private WebTarget webTarget; + + @Override + public String getImportSource() { + return "datacite"; + } + + @Override + public void init() throws Exception { + Client client = ClientBuilder.newClient(); + webTarget = client.target("https://api.datacite.org/dois/"); + } + + @Override + public ImportRecord getRecord(String recordId) throws MetadataSourceException { + List records = null; + String id = getID(recordId); + if (StringUtils.isNotBlank(id)) { + records = retry(new SearchByIdCallable(id)); + } else { + records = retry(new SearchByIdCallable(recordId)); + } + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public int getRecordsCount(String query) throws MetadataSourceException { + String id = getID(query); + if (StringUtils.isNotBlank(id)) { + return retry(new DoiCheckCallable(id)); + } + return retry(new CountByQueryCallable(query)); + } + + @Override + public int getRecordsCount(Query query) throws MetadataSourceException { + String id = getID(query.toString()); + if (StringUtils.isNotBlank(id)) { + return retry(new DoiCheckCallable(id)); + } + return retry(new CountByQueryCallable(query)); + } + + + @Override + public Collection getRecords(String query, int start, int count) throws MetadataSourceException { + String id = getID(query.toString()); + if (StringUtils.isNotBlank(id)) { + return retry(new SearchByIdCallable(id)); + } + return retry(new SearchByQueryCallable(query, count, start)); + } + + @Override + public Collection getRecords(Query query) throws MetadataSourceException { + String id = getID(query.toString()); + if (StringUtils.isNotBlank(id)) { + return retry(new SearchByIdCallable(id)); + } + return retry(new SearchByQueryCallable(query)); + } + + @Override + public ImportRecord getRecord(Query query) throws MetadataSourceException { + List records = null; + String id = getID(query.toString()); + if (StringUtils.isNotBlank(id)) { + records = retry(new SearchByIdCallable(id)); + } else { + records = retry(new SearchByIdCallable(query)); + } + return records == null || records.isEmpty() ? null : records.get(0); + } + + @Override + public Collection findMatchingRecords(Query query) throws MetadataSourceException { + String id = getID(query.toString()); + if (StringUtils.isNotBlank(id)) { + return retry(new SearchByIdCallable(id)); + } + return retry(new FindMatchingRecordCallable(query)); + } + + + @Override + public Collection findMatchingRecords(Item item) throws MetadataSourceException { + throw new MethodNotFoundException("This method is not implemented for Datacite"); + } + + public String getID(String query) { + if (DoiCheck.isDoi(query)) { + return query; + } + //Workaround for encoded slashes. + if (query.contains("%252F")) { + query = query.replace("%252F", "/"); + } + if (DoiCheck.isDoi(query)) { + return query; + } + return StringUtils.EMPTY; + } + + private class SearchByQueryCallable implements Callable> { + + private Query query; + + private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) { + query = new Query(); + query.addParameter("query", queryString); + query.addParameter("count", maxResult); + query.addParameter("start", start); + } + + private SearchByQueryCallable(Query query) { + this.query = query; + } + + @Override + public List call() throws Exception { + List results = new ArrayList<>(); + HttpGet method = null; + try { + Integer count = query.getParameterAsClass("count", Integer.class); + Integer start = query.getParameterAsClass("start", Integer.class); + WebTarget local = webTarget.queryParam("query", query.getParameterAsClass("query", String.class)); + if (count != null) { + local = local.queryParam("rows", count); + } + if (start != null) { + local = local.queryParam("offset", start); + } + Invocation.Builder invocationBuilder = local.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() != 200) { + return null; + } + String responseString = response.readEntity(String.class); + ReadContext ctx = JsonPath.parse(responseString); + Object o = ctx.read("$.data.attributes"); + if (o.getClass().isAssignableFrom(JSONArray.class)) { + JSONArray array = (JSONArray)o; + int size = array.size(); + for (int index = 0; index < size; index++) { + Gson gson = new Gson(); + String innerJson = gson.toJson(array.get(index), LinkedHashMap.class); + results.add(transformSourceRecords(innerJson)); + } + } else { + results.add(transformSourceRecords(o.toString())); + } + return results; + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } finally { + if (method != null) { + method.releaseConnection(); + } + } + } + + } + + private class SearchByIdCallable implements Callable> { + private Query query; + + private SearchByIdCallable(Query query) { + this.query = query; + } + + private SearchByIdCallable(String id) { + this.query = new Query(); + query.addParameter("id", id); + } + + @Override + public List call() throws Exception { + List results = new ArrayList<>(); + HttpGet method = null; + try { + WebTarget local = webTarget.path(query.getParameterAsClass("id", String.class)); + Invocation.Builder invocationBuilder = local.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() != 200) { + return null; + } + String responseString = response.readEntity(String.class); + ReadContext ctx = JsonPath.parse(responseString); + Object o = ctx.read("$.data.attributes"); + if (o.getClass().isAssignableFrom(JSONArray.class)) { + JSONArray array = (JSONArray)o; + int size = array.size(); + for (int index = 0; index < size; index++) { + Gson gson = new Gson(); + String innerJson = gson.toJson(array.get(index), LinkedHashMap.class); + results.add(transformSourceRecords(innerJson)); + } + } else { + Gson gson = new Gson(); + results.add(transformSourceRecords(gson.toJson(o, Object.class))); + } + return results; + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } finally { + if (method != null) { + method.releaseConnection(); + } + } + } + } + + private class FindMatchingRecordCallable implements Callable> { + + private Query query; + + private FindMatchingRecordCallable(Query q) { + query = q; + } + + @Override + public List call() throws Exception { + String queryValue = query.getParameterAsClass("query", String.class); + Integer count = query.getParameterAsClass("count", Integer.class); + Integer start = query.getParameterAsClass("start", Integer.class); + List results = new ArrayList<>(); + HttpGet method = null; + try { + WebTarget local = webTarget; + if (queryValue != null) { + local = local.queryParam("query", queryValue); + } + if (count != null) { + local = local.queryParam("page[size]", count); + } + if (start != null) { + local = local.queryParam("page[number]", start); + } + Invocation.Builder invocationBuilder = local.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() != 200) { + return null; + } + String responseString = response.readEntity(String.class); + ReadContext ctx = JsonPath.parse(responseString); + Object o = ctx.read("$.data.attributes"); + if (o.getClass().isAssignableFrom(JSONArray.class)) { + JSONArray array = (JSONArray)o; + int size = array.size(); + for (int index = 0; index < size; index++) { + Gson gson = new Gson(); + String innerJson = gson.toJson(array.get(index), LinkedHashMap.class); + results.add(transformSourceRecords(innerJson)); + } + } else { + results.add(transformSourceRecords(o.toString())); + } + return results; + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } finally { + if (method != null) { + method.releaseConnection(); + } + } + } + + } + + private class CountByQueryCallable implements Callable { + private Query query; + + + private CountByQueryCallable(String queryString) { + query = new Query(); + query.addParameter("query", queryString); + } + + private CountByQueryCallable(Query query) { + this.query = query; + } + + + @Override + public Integer call() throws Exception { + HttpGet method = null; + try { + WebTarget local = webTarget.queryParam("query", query.getParameterAsClass("query", String.class)); + Invocation.Builder invocationBuilder = local.request(); + Response response = invocationBuilder.get(); + if (response.getStatus() != 200) { + return null; + } + String responseString = response.readEntity(String.class); + ReadContext ctx = JsonPath.parse(responseString); + return ctx.read("$.meta.total"); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } finally { + if (method != null) { + method.releaseConnection(); + } + } + } + } + + private class DoiCheckCallable implements Callable { + + private final Query query; + + private DoiCheckCallable(final String id) { + final Query query = new Query(); + query.addParameter("id", id); + this.query = query; + } + + private DoiCheckCallable(final Query query) { + this.query = query; + } + + @Override + public Integer call() throws Exception { + WebTarget local = webTarget.path(query.getParameterAsClass("id", String.class)); + Invocation.Builder invocationBuilder = local.request(); + Response response = invocationBuilder.head(); + return response.getStatus() == 200 ? 1 : 0; + } + } + +} diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index d5ba4bd462c4..b8f02c46fff9 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -34,6 +34,14 @@ + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml index bd6da8ad8a59..dabbde22ec93 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml @@ -91,4 +91,15 @@ - \ No newline at end of file + + + + + + + Publication + none + + + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java index ce75d10ea442..65492cbbe869 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ExternalSourcesRestControllerIT.java @@ -53,7 +53,7 @@ public void findAllExternalSources() throws Exception { ExternalSourceMatcher.matchExternalSource( "openAIREFunding", "openAIREFunding", false) ))) - .andExpect(jsonPath("$.page.totalElements", Matchers.is(9))); + .andExpect(jsonPath("$.page.totalElements", Matchers.is(10))); } @Test diff --git a/dspace/config/spring/api/datacite-integration.xml b/dspace/config/spring/api/datacite-integration.xml new file mode 100644 index 000000000000..e1efa3eadbd9 --- /dev/null +++ b/dspace/config/spring/api/datacite-integration.xml @@ -0,0 +1,43 @@ + + + + + + + + Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it + only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over + what metadatafield is generated. + + + + + + + + + + + + + + + + + + + + + + + + From 360bc85ead8207a09ea37e705b000b7f24224f83 Mon Sep 17 00:00:00 2001 From: Philipp Rumpf Date: Thu, 8 Dec 2022 14:57:15 +0000 Subject: [PATCH 122/394] Merge pull request #5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * DataCite plugin for import * start copying Crossref test * use dataCite original JSON * snapshot * adjust DataCite mapping to grab all titles * simplify DataCiteImportMetadataSourceServiceImpl. Avoid GSON, JsonPat… * rewrite DataCiteImportMetadataSourceServiceIT based on CrossRef IT. --- ...taCiteImportMetadataSourceServiceImpl.java | 344 ++++-------------- .../config/spring/api/external-services.xml | 1 + ...DataCiteImportMetadataSourceServiceIT.java | 130 +++++++ .../org/dspace/app/rest/dataCite-test.json | 1 + .../spring/api/datacite-integration.xml | 7 +- 5 files changed, 203 insertions(+), 280 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java create mode 100644 dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java index b4235f1fe838..59214ac1176e 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java @@ -9,29 +9,28 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedHashMap; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; -import java.util.concurrent.Callable; +import java.util.Map; import javax.el.MethodNotFoundException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Response; -import com.google.gson.Gson; -import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.ReadContext; -import net.minidev.json.JSONArray; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; -import org.apache.http.client.methods.HttpGet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.importer.external.datamodel.ImportRecord; import org.dspace.importer.external.datamodel.Query; import org.dspace.importer.external.exception.MetadataSourceException; +import org.dspace.importer.external.liveimportclient.service.LiveImportClient; import org.dspace.importer.external.service.AbstractImportMetadataSourceService; import org.dspace.importer.external.service.DoiCheck; import org.dspace.importer.external.service.components.QuerySource; +import org.springframework.beans.factory.annotation.Autowired; /** * Implements a data source for querying Datacite @@ -39,13 +38,21 @@ * * optional Affiliation informations are not part of the API request. * https://support.datacite.org/docs/can-i-see-more-detailed-affiliation-information-in-the-rest-api - * + * * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it) * @author Florian Gantner (florian.gantner@uni-bamberg.de) * */ public class DataCiteImportMetadataSourceServiceImpl - extends AbstractImportMetadataSourceService implements QuerySource { + extends AbstractImportMetadataSourceService implements QuerySource { + private final static Logger log = LogManager.getLogger(); + + @Autowired + private LiveImportClient liveImportClient; + + private final int timeoutMs = 180000; + + private final String url = "https://api.datacite.org/dois/"; private WebTarget webTarget; @@ -56,91 +63,97 @@ public String getImportSource() { @Override public void init() throws Exception { - Client client = ClientBuilder.newClient(); - webTarget = client.target("https://api.datacite.org/dois/"); } @Override public ImportRecord getRecord(String recordId) throws MetadataSourceException { - List records = null; - String id = getID(recordId); - if (StringUtils.isNotBlank(id)) { - records = retry(new SearchByIdCallable(id)); - } else { - records = retry(new SearchByIdCallable(recordId)); + Collection records = getRecords(recordId, 0, 1); + if (records.size() == 0) { + return null; } - return records == null || records.isEmpty() ? null : records.get(0); + return records.stream().findFirst().get(); } @Override public int getRecordsCount(String query) throws MetadataSourceException { - String id = getID(query); - if (StringUtils.isNotBlank(id)) { - return retry(new DoiCheckCallable(id)); - } - return retry(new CountByQueryCallable(query)); + Collection records = getRecords(query, 0, -1); + return records == null ? 0 : records.size(); } @Override public int getRecordsCount(Query query) throws MetadataSourceException { String id = getID(query.toString()); - if (StringUtils.isNotBlank(id)) { - return retry(new DoiCheckCallable(id)); - } - return retry(new CountByQueryCallable(query)); + return getRecordsCount(StringUtils.isBlank(id) ? query.toString() : id); } @Override public Collection getRecords(String query, int start, int count) throws MetadataSourceException { - String id = getID(query.toString()); - if (StringUtils.isNotBlank(id)) { - return retry(new SearchByIdCallable(id)); + List records = new ArrayList<>(); + String id = getID(query); + Map> params = new HashMap<>(); + Map uriParameters = new HashMap<>(); + params.put("uriParameters", uriParameters); + if (StringUtils.isBlank(id)) { + id = query; + } + uriParameters.put("query", id); + String responseString = liveImportClient.executeHttpGetRequest(timeoutMs, url, params); + JsonNode jsonNode = convertStringJsonToJsonNode(responseString); + JsonNode dataNode = jsonNode.at("/data"); + if (dataNode.isArray()) { + Iterator iterator = dataNode.iterator(); + while (iterator.hasNext()) { + JsonNode singleDoiNode = iterator.next(); + String json = singleDoiNode.at("/attributes").toString(); + records.add(transformSourceRecords(json)); + } + } else { + String json = dataNode.at("/attributes").toString(); + records.add(transformSourceRecords(json)); } - return retry(new SearchByQueryCallable(query, count, start)); + + return records; + } + + private JsonNode convertStringJsonToJsonNode(String json) { + try { + return new ObjectMapper().readTree(json); + } catch (JsonProcessingException e) { + log.error("Unable to process json response.", e); + } + return null; } @Override public Collection getRecords(Query query) throws MetadataSourceException { String id = getID(query.toString()); - if (StringUtils.isNotBlank(id)) { - return retry(new SearchByIdCallable(id)); - } - return retry(new SearchByQueryCallable(query)); + return getRecords(StringUtils.isBlank(id) ? query.toString() : id, 0, -1); } @Override public ImportRecord getRecord(Query query) throws MetadataSourceException { - List records = null; String id = getID(query.toString()); - if (StringUtils.isNotBlank(id)) { - records = retry(new SearchByIdCallable(id)); - } else { - records = retry(new SearchByIdCallable(query)); - } - return records == null || records.isEmpty() ? null : records.get(0); + return getRecord(StringUtils.isBlank(id) ? query.toString() : id); } @Override public Collection findMatchingRecords(Query query) throws MetadataSourceException { String id = getID(query.toString()); - if (StringUtils.isNotBlank(id)) { - return retry(new SearchByIdCallable(id)); - } - return retry(new FindMatchingRecordCallable(query)); + return getRecords(StringUtils.isBlank(id) ? query.toString() : id, 0, -1); } @Override public Collection findMatchingRecords(Item item) throws MetadataSourceException { - throw new MethodNotFoundException("This method is not implemented for Datacite"); + throw new MethodNotFoundException("This method is not implemented for DataCite"); } public String getID(String query) { if (DoiCheck.isDoi(query)) { return query; } - //Workaround for encoded slashes. + // Workaround for encoded slashes. if (query.contains("%252F")) { query = query.replace("%252F", "/"); } @@ -149,231 +162,4 @@ public String getID(String query) { } return StringUtils.EMPTY; } - - private class SearchByQueryCallable implements Callable> { - - private Query query; - - private SearchByQueryCallable(String queryString, Integer maxResult, Integer start) { - query = new Query(); - query.addParameter("query", queryString); - query.addParameter("count", maxResult); - query.addParameter("start", start); - } - - private SearchByQueryCallable(Query query) { - this.query = query; - } - - @Override - public List call() throws Exception { - List results = new ArrayList<>(); - HttpGet method = null; - try { - Integer count = query.getParameterAsClass("count", Integer.class); - Integer start = query.getParameterAsClass("start", Integer.class); - WebTarget local = webTarget.queryParam("query", query.getParameterAsClass("query", String.class)); - if (count != null) { - local = local.queryParam("rows", count); - } - if (start != null) { - local = local.queryParam("offset", start); - } - Invocation.Builder invocationBuilder = local.request(); - Response response = invocationBuilder.get(); - if (response.getStatus() != 200) { - return null; - } - String responseString = response.readEntity(String.class); - ReadContext ctx = JsonPath.parse(responseString); - Object o = ctx.read("$.data.attributes"); - if (o.getClass().isAssignableFrom(JSONArray.class)) { - JSONArray array = (JSONArray)o; - int size = array.size(); - for (int index = 0; index < size; index++) { - Gson gson = new Gson(); - String innerJson = gson.toJson(array.get(index), LinkedHashMap.class); - results.add(transformSourceRecords(innerJson)); - } - } else { - results.add(transformSourceRecords(o.toString())); - } - return results; - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } finally { - if (method != null) { - method.releaseConnection(); - } - } - } - - } - - private class SearchByIdCallable implements Callable> { - private Query query; - - private SearchByIdCallable(Query query) { - this.query = query; - } - - private SearchByIdCallable(String id) { - this.query = new Query(); - query.addParameter("id", id); - } - - @Override - public List call() throws Exception { - List results = new ArrayList<>(); - HttpGet method = null; - try { - WebTarget local = webTarget.path(query.getParameterAsClass("id", String.class)); - Invocation.Builder invocationBuilder = local.request(); - Response response = invocationBuilder.get(); - if (response.getStatus() != 200) { - return null; - } - String responseString = response.readEntity(String.class); - ReadContext ctx = JsonPath.parse(responseString); - Object o = ctx.read("$.data.attributes"); - if (o.getClass().isAssignableFrom(JSONArray.class)) { - JSONArray array = (JSONArray)o; - int size = array.size(); - for (int index = 0; index < size; index++) { - Gson gson = new Gson(); - String innerJson = gson.toJson(array.get(index), LinkedHashMap.class); - results.add(transformSourceRecords(innerJson)); - } - } else { - Gson gson = new Gson(); - results.add(transformSourceRecords(gson.toJson(o, Object.class))); - } - return results; - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } finally { - if (method != null) { - method.releaseConnection(); - } - } - } - } - - private class FindMatchingRecordCallable implements Callable> { - - private Query query; - - private FindMatchingRecordCallable(Query q) { - query = q; - } - - @Override - public List call() throws Exception { - String queryValue = query.getParameterAsClass("query", String.class); - Integer count = query.getParameterAsClass("count", Integer.class); - Integer start = query.getParameterAsClass("start", Integer.class); - List results = new ArrayList<>(); - HttpGet method = null; - try { - WebTarget local = webTarget; - if (queryValue != null) { - local = local.queryParam("query", queryValue); - } - if (count != null) { - local = local.queryParam("page[size]", count); - } - if (start != null) { - local = local.queryParam("page[number]", start); - } - Invocation.Builder invocationBuilder = local.request(); - Response response = invocationBuilder.get(); - if (response.getStatus() != 200) { - return null; - } - String responseString = response.readEntity(String.class); - ReadContext ctx = JsonPath.parse(responseString); - Object o = ctx.read("$.data.attributes"); - if (o.getClass().isAssignableFrom(JSONArray.class)) { - JSONArray array = (JSONArray)o; - int size = array.size(); - for (int index = 0; index < size; index++) { - Gson gson = new Gson(); - String innerJson = gson.toJson(array.get(index), LinkedHashMap.class); - results.add(transformSourceRecords(innerJson)); - } - } else { - results.add(transformSourceRecords(o.toString())); - } - return results; - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } finally { - if (method != null) { - method.releaseConnection(); - } - } - } - - } - - private class CountByQueryCallable implements Callable { - private Query query; - - - private CountByQueryCallable(String queryString) { - query = new Query(); - query.addParameter("query", queryString); - } - - private CountByQueryCallable(Query query) { - this.query = query; - } - - - @Override - public Integer call() throws Exception { - HttpGet method = null; - try { - WebTarget local = webTarget.queryParam("query", query.getParameterAsClass("query", String.class)); - Invocation.Builder invocationBuilder = local.request(); - Response response = invocationBuilder.get(); - if (response.getStatus() != 200) { - return null; - } - String responseString = response.readEntity(String.class); - ReadContext ctx = JsonPath.parse(responseString); - return ctx.read("$.meta.total"); - } catch (Exception e) { - throw new RuntimeException(e.getMessage(), e); - } finally { - if (method != null) { - method.releaseConnection(); - } - } - } - } - - private class DoiCheckCallable implements Callable { - - private final Query query; - - private DoiCheckCallable(final String id) { - final Query query = new Query(); - query.addParameter("id", id); - this.query = query; - } - - private DoiCheckCallable(final Query query) { - this.query = query; - } - - @Override - public Integer call() throws Exception { - WebTarget local = webTarget.path(query.getParameterAsClass("id", String.class)); - Invocation.Builder invocationBuilder = local.request(); - Response response = invocationBuilder.head(); - return response.getStatus() == 200 ? 1 : 0; - } - } - } diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml index dabbde22ec93..37e1fb508953 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-services.xml @@ -103,3 +103,4 @@ + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java new file mode 100644 index 000000000000..466b14a863b9 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java @@ -0,0 +1,130 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.el.MethodNotFoundException; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Item; +import org.dspace.importer.external.datacite.DataCiteImportMetadataSourceServiceImpl; +import org.dspace.importer.external.datamodel.ImportRecord; +import org.dspace.importer.external.liveimportclient.service.LiveImportClientImpl; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Integration tests for {@link DataCiteImportMetadataSourceServiceImpl} + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class DataCiteImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest { + + @Autowired + private LiveImportClientImpl liveImportClientImpl; + + @Autowired + private DataCiteImportMetadataSourceServiceImpl dataCiteServiceImpl; + + @Test + public void dataCiteImportMetadataGetRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + try (InputStream dataCiteResp = getClass().getResourceAsStream("dataCite-test.json")) { + String dataCiteRespXmlResp = IOUtils.toString(dataCiteResp, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(dataCiteRespXmlResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + ArrayList collection2match = getRecords(); + Collection recordsImported = dataCiteServiceImpl.getRecords("10.48550/arxiv.2207.04779", + 0, -1); + assertEquals(1, recordsImported.size()); + matchRecords(new ArrayList<>(recordsImported), collection2match); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + @Test + public void dataCiteImportMetadataGetRecordsCountTest() throws Exception { + context.turnOffAuthorisationSystem(); + CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient(); + CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); + try (InputStream dataciteResp = getClass().getResourceAsStream("dataCite-test.json")) { + String dataciteTextResp = IOUtils.toString(dataciteResp, Charset.defaultCharset()); + + liveImportClientImpl.setHttpClient(httpClient); + CloseableHttpResponse response = mockResponse(dataciteTextResp, 200, "OK"); + when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response); + + context.restoreAuthSystemState(); + int tot = dataCiteServiceImpl.getRecordsCount("10.48550/arxiv.2207.04779"); + assertEquals(1, tot); + } finally { + liveImportClientImpl.setHttpClient(originalHttpClient); + } + } + + @Test(expected = MethodNotFoundException.class) + public void dataCiteImportMetadataFindMatchingRecordsTest() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + org.dspace.content.Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item testItem = ItemBuilder.createItem(context, col1) + .withTitle("test item") + .withIssueDate("2021") + .build(); + + context.restoreAuthSystemState(); + dataCiteServiceImpl.findMatchingRecords(testItem); + } + + private ArrayList getRecords() { + ArrayList records = new ArrayList<>(); + //define first record + List metadatums = new ArrayList<>(); + MetadatumDTO title = createMetadatumDTO("dc", "title", null, + "Mathematical Proof Between Generations"); + MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.48550/arxiv.2207.04779"); + + metadatums.add(title); + metadatums.add(doi); + + ImportRecord firstRecord = new ImportRecord(metadatums); + + records.add(firstRecord); + return records; + } + +} diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json new file mode 100644 index 000000000000..8ede6f29a08e --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/dataCite-test.json @@ -0,0 +1 @@ +{"data":{"id":"10.48550/arxiv.2207.04779","type":"dois","attributes":{"doi":"10.48550/arxiv.2207.04779","prefix":"10.48550","suffix":"arxiv.2207.04779","identifiers":[{"identifier":"2207.04779","identifierType":"arXiv"}],"alternateIdentifiers":[{"alternateIdentifierType":"arXiv","alternateIdentifier":"2207.04779"}],"creators":[{"name":"Bayer, Jonas","nameType":"Personal","givenName":"Jonas","familyName":"Bayer","affiliation":[],"nameIdentifiers":[]},{"name":"Benzmüller, Christoph","nameType":"Personal","givenName":"Christoph","familyName":"Benzmüller","affiliation":[],"nameIdentifiers":[]},{"name":"Buzzard, Kevin","nameType":"Personal","givenName":"Kevin","familyName":"Buzzard","affiliation":[],"nameIdentifiers":[]},{"name":"David, Marco","nameType":"Personal","givenName":"Marco","familyName":"David","affiliation":[],"nameIdentifiers":[]},{"name":"Lamport, Leslie","nameType":"Personal","givenName":"Leslie","familyName":"Lamport","affiliation":[],"nameIdentifiers":[]},{"name":"Matiyasevich, Yuri","nameType":"Personal","givenName":"Yuri","familyName":"Matiyasevich","affiliation":[],"nameIdentifiers":[]},{"name":"Paulson, Lawrence","nameType":"Personal","givenName":"Lawrence","familyName":"Paulson","affiliation":[],"nameIdentifiers":[]},{"name":"Schleicher, Dierk","nameType":"Personal","givenName":"Dierk","familyName":"Schleicher","affiliation":[],"nameIdentifiers":[]},{"name":"Stock, Benedikt","nameType":"Personal","givenName":"Benedikt","familyName":"Stock","affiliation":[],"nameIdentifiers":[]},{"name":"Zelmanov, Efim","nameType":"Personal","givenName":"Efim","familyName":"Zelmanov","affiliation":[],"nameIdentifiers":[]}],"titles":[{"title":"Mathematical Proof Between Generations"}],"publisher":"arXiv","container":{},"publicationYear":2022,"subjects":[{"lang":"en","subject":"History and Overview (math.HO)","subjectScheme":"arXiv"},{"lang":"en","subject":"Logic in Computer Science (cs.LO)","subjectScheme":"arXiv"},{"subject":"FOS: Mathematics","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Mathematics","schemeUri":"http://www.oecd.org/science/inno/38235147.pdf","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Computer and information sciences","subjectScheme":"Fields of Science and Technology (FOS)"},{"subject":"FOS: Computer and information sciences","schemeUri":"http://www.oecd.org/science/inno/38235147.pdf","subjectScheme":"Fields of Science and Technology (FOS)"}],"contributors":[],"dates":[{"date":"2022-07-08T14:42:33Z","dateType":"Submitted","dateInformation":"v1"},{"date":"2022-07-13T00:14:24Z","dateType":"Updated","dateInformation":"v1"},{"date":"2022-07","dateType":"Available","dateInformation":"v1"},{"date":"2022","dateType":"Issued"}],"language":null,"types":{"ris":"GEN","bibtex":"misc","citeproc":"article","schemaOrg":"CreativeWork","resourceType":"Article","resourceTypeGeneral":"Preprint"},"relatedIdentifiers":[],"relatedItems":[],"sizes":[],"formats":[],"version":"1","rightsList":[{"rights":"arXiv.org perpetual, non-exclusive license","rightsUri":"http://arxiv.org/licenses/nonexclusive-distrib/1.0/"}],"descriptions":[{"description":"A proof is one of the most important concepts of mathematics. However, there is a striking difference between how a proof is defined in theory and how it is used in practice. This puts the unique status of mathematics as exact science into peril. Now may be the time to reconcile theory and practice, i.e. precision and intuition, through the advent of computer proof assistants. For the most time this has been a topic for experts in specialized communities. However, mathematical proofs have become increasingly sophisticated, stretching the boundaries of what is humanly comprehensible, so that leading mathematicians have asked for formal verification of their proofs. At the same time, major theorems in mathematics have recently been computer-verified by people from outside of these communities, even by beginning students. This article investigates the gap between the different definitions of a proof and possibilities to build bridges. It is written as a polemic or a collage by different members of the communities in mathematics and computer science at different stages of their careers, challenging well-known preconceptions and exploring new perspectives.","descriptionType":"Abstract"},{"description":"17 pages, 1 figure","descriptionType":"Other"}],"geoLocations":[],"fundingReferences":[],"xml":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHJlc291cmNlIHhtbG5zPSJodHRwOi8vZGF0YWNpdGUub3JnL3NjaGVtYS9rZXJuZWwtNCIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnNjaGVtYUxvY2F0aW9uPSJodHRwOi8vZGF0YWNpdGUub3JnL3NjaGVtYS9rZXJuZWwtNCBodHRwOi8vc2NoZW1hLmRhdGFjaXRlLm9yZy9tZXRhL2tlcm5lbC00LjMvbWV0YWRhdGEueHNkIj4KICA8aWRlbnRpZmllciBpZGVudGlmaWVyVHlwZT0iRE9JIj4xMC40ODU1MC9BUlhJVi4yMjA3LjA0Nzc5PC9pZGVudGlmaWVyPgogIDxhbHRlcm5hdGVJZGVudGlmaWVycz4KICAgIDxhbHRlcm5hdGVJZGVudGlmaWVyIGFsdGVybmF0ZUlkZW50aWZpZXJUeXBlPSJhclhpdiI+MjIwNy4wNDc3OTwvYWx0ZXJuYXRlSWRlbnRpZmllcj4KICA8L2FsdGVybmF0ZUlkZW50aWZpZXJzPgogIDxjcmVhdG9ycz4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5CYXllciwgSm9uYXM8L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPkpvbmFzPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPkJheWVyPC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZSBuYW1lVHlwZT0iUGVyc29uYWwiPkJlbnptw7xsbGVyLCBDaHJpc3RvcGg8L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPkNocmlzdG9waDwvZ2l2ZW5OYW1lPgogICAgICA8ZmFtaWx5TmFtZT5CZW56bcO8bGxlcjwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5CdXp6YXJkLCBLZXZpbjwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+S2V2aW48L2dpdmVuTmFtZT4KICAgICAgPGZhbWlseU5hbWU+QnV6emFyZDwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5EYXZpZCwgTWFyY288L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPk1hcmNvPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPkRhdmlkPC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZSBuYW1lVHlwZT0iUGVyc29uYWwiPkxhbXBvcnQsIExlc2xpZTwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+TGVzbGllPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPkxhbXBvcnQ8L2ZhbWlseU5hbWU+CiAgICA8L2NyZWF0b3I+CiAgICA8Y3JlYXRvcj4KICAgICAgPGNyZWF0b3JOYW1lIG5hbWVUeXBlPSJQZXJzb25hbCI+TWF0aXlhc2V2aWNoLCBZdXJpPC9jcmVhdG9yTmFtZT4KICAgICAgPGdpdmVuTmFtZT5ZdXJpPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPk1hdGl5YXNldmljaDwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5QYXVsc29uLCBMYXdyZW5jZTwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+TGF3cmVuY2U8L2dpdmVuTmFtZT4KICAgICAgPGZhbWlseU5hbWU+UGF1bHNvbjwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5TY2hsZWljaGVyLCBEaWVyazwvY3JlYXRvck5hbWU+CiAgICAgIDxnaXZlbk5hbWU+RGllcms8L2dpdmVuTmFtZT4KICAgICAgPGZhbWlseU5hbWU+U2NobGVpY2hlcjwvZmFtaWx5TmFtZT4KICAgIDwvY3JlYXRvcj4KICAgIDxjcmVhdG9yPgogICAgICA8Y3JlYXRvck5hbWUgbmFtZVR5cGU9IlBlcnNvbmFsIj5TdG9jaywgQmVuZWRpa3Q8L2NyZWF0b3JOYW1lPgogICAgICA8Z2l2ZW5OYW1lPkJlbmVkaWt0PC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPlN0b2NrPC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogICAgPGNyZWF0b3I+CiAgICAgIDxjcmVhdG9yTmFtZSBuYW1lVHlwZT0iUGVyc29uYWwiPlplbG1hbm92LCBFZmltPC9jcmVhdG9yTmFtZT4KICAgICAgPGdpdmVuTmFtZT5FZmltPC9naXZlbk5hbWU+CiAgICAgIDxmYW1pbHlOYW1lPlplbG1hbm92PC9mYW1pbHlOYW1lPgogICAgPC9jcmVhdG9yPgogIDwvY3JlYXRvcnM+CiAgPHRpdGxlcz4KICAgIDx0aXRsZT5NYXRoZW1hdGljYWwgUHJvb2YgQmV0d2VlbiBHZW5lcmF0aW9uczwvdGl0bGU+CiAgPC90aXRsZXM+CiAgPHB1Ymxpc2hlcj5hclhpdjwvcHVibGlzaGVyPgogIDxwdWJsaWNhdGlvblllYXI+MjAyMjwvcHVibGljYXRpb25ZZWFyPgogIDxzdWJqZWN0cz4KICAgIDxzdWJqZWN0IHhtbDpsYW5nPSJlbiIgc3ViamVjdFNjaGVtZT0iYXJYaXYiPkhpc3RvcnkgYW5kIE92ZXJ2aWV3IChtYXRoLkhPKTwvc3ViamVjdD4KICAgIDxzdWJqZWN0IHhtbDpsYW5nPSJlbiIgc3ViamVjdFNjaGVtZT0iYXJYaXYiPkxvZ2ljIGluIENvbXB1dGVyIFNjaWVuY2UgKGNzLkxPKTwvc3ViamVjdD4KICAgIDxzdWJqZWN0IHN1YmplY3RTY2hlbWU9IkZpZWxkcyBvZiBTY2llbmNlIGFuZCBUZWNobm9sb2d5IChGT1MpIj5GT1M6IE1hdGhlbWF0aWNzPC9zdWJqZWN0PgogICAgPHN1YmplY3Qgc3ViamVjdFNjaGVtZT0iRmllbGRzIG9mIFNjaWVuY2UgYW5kIFRlY2hub2xvZ3kgKEZPUykiPkZPUzogQ29tcHV0ZXIgYW5kIGluZm9ybWF0aW9uIHNjaWVuY2VzPC9zdWJqZWN0PgogIDwvc3ViamVjdHM+CiAgPGRhdGVzPgogICAgPGRhdGUgZGF0ZVR5cGU9IlN1Ym1pdHRlZCIgZGF0ZUluZm9ybWF0aW9uPSJ2MSI+MjAyMi0wNy0wOFQxNDo0MjozM1o8L2RhdGU+CiAgICA8ZGF0ZSBkYXRlVHlwZT0iVXBkYXRlZCIgZGF0ZUluZm9ybWF0aW9uPSJ2MSI+MjAyMi0wNy0xM1QwMDoxNDoyNFo8L2RhdGU+CiAgICA8ZGF0ZSBkYXRlVHlwZT0iQXZhaWxhYmxlIiBkYXRlSW5mb3JtYXRpb249InYxIj4yMDIyLTA3PC9kYXRlPgogIDwvZGF0ZXM+CiAgPHJlc291cmNlVHlwZSByZXNvdXJjZVR5cGVHZW5lcmFsPSJQcmVwcmludCI+QXJ0aWNsZTwvcmVzb3VyY2VUeXBlPgogIDx2ZXJzaW9uPjE8L3ZlcnNpb24+CiAgPHJpZ2h0c0xpc3Q+CiAgICA8cmlnaHRzIHJpZ2h0c1VSST0iaHR0cDovL2FyeGl2Lm9yZy9saWNlbnNlcy9ub25leGNsdXNpdmUtZGlzdHJpYi8xLjAvIj5hclhpdi5vcmcgcGVycGV0dWFsLCBub24tZXhjbHVzaXZlIGxpY2Vuc2U8L3JpZ2h0cz4KICA8L3JpZ2h0c0xpc3Q+CiAgPGRlc2NyaXB0aW9ucz4KICAgIDxkZXNjcmlwdGlvbiBkZXNjcmlwdGlvblR5cGU9IkFic3RyYWN0Ij5BIHByb29mIGlzIG9uZSBvZiB0aGUgbW9zdCBpbXBvcnRhbnQgY29uY2VwdHMgb2YgbWF0aGVtYXRpY3MuIEhvd2V2ZXIsIHRoZXJlIGlzIGEgc3RyaWtpbmcgZGlmZmVyZW5jZSBiZXR3ZWVuIGhvdyBhIHByb29mIGlzIGRlZmluZWQgaW4gdGhlb3J5IGFuZCBob3cgaXQgaXMgdXNlZCBpbiBwcmFjdGljZS4gVGhpcyBwdXRzIHRoZSB1bmlxdWUgc3RhdHVzIG9mIG1hdGhlbWF0aWNzIGFzIGV4YWN0IHNjaWVuY2UgaW50byBwZXJpbC4gTm93IG1heSBiZSB0aGUgdGltZSB0byByZWNvbmNpbGUgdGhlb3J5IGFuZCBwcmFjdGljZSwgaS5lLiBwcmVjaXNpb24gYW5kIGludHVpdGlvbiwgdGhyb3VnaCB0aGUgYWR2ZW50IG9mIGNvbXB1dGVyIHByb29mIGFzc2lzdGFudHMuIEZvciB0aGUgbW9zdCB0aW1lIHRoaXMgaGFzIGJlZW4gYSB0b3BpYyBmb3IgZXhwZXJ0cyBpbiBzcGVjaWFsaXplZCBjb21tdW5pdGllcy4gSG93ZXZlciwgbWF0aGVtYXRpY2FsIHByb29mcyBoYXZlIGJlY29tZSBpbmNyZWFzaW5nbHkgc29waGlzdGljYXRlZCwgc3RyZXRjaGluZyB0aGUgYm91bmRhcmllcyBvZiB3aGF0IGlzIGh1bWFubHkgY29tcHJlaGVuc2libGUsIHNvIHRoYXQgbGVhZGluZyBtYXRoZW1hdGljaWFucyBoYXZlIGFza2VkIGZvciBmb3JtYWwgdmVyaWZpY2F0aW9uIG9mIHRoZWlyIHByb29mcy4gQXQgdGhlIHNhbWUgdGltZSwgbWFqb3IgdGhlb3JlbXMgaW4gbWF0aGVtYXRpY3MgaGF2ZSByZWNlbnRseSBiZWVuIGNvbXB1dGVyLXZlcmlmaWVkIGJ5IHBlb3BsZSBmcm9tIG91dHNpZGUgb2YgdGhlc2UgY29tbXVuaXRpZXMsIGV2ZW4gYnkgYmVnaW5uaW5nIHN0dWRlbnRzLiBUaGlzIGFydGljbGUgaW52ZXN0aWdhdGVzIHRoZSBnYXAgYmV0d2VlbiB0aGUgZGlmZmVyZW50IGRlZmluaXRpb25zIG9mIGEgcHJvb2YgYW5kIHBvc3NpYmlsaXRpZXMgdG8gYnVpbGQgYnJpZGdlcy4gSXQgaXMgd3JpdHRlbiBhcyBhIHBvbGVtaWMgb3IgYSBjb2xsYWdlIGJ5IGRpZmZlcmVudCBtZW1iZXJzIG9mIHRoZSBjb21tdW5pdGllcyBpbiBtYXRoZW1hdGljcyBhbmQgY29tcHV0ZXIgc2NpZW5jZSBhdCBkaWZmZXJlbnQgc3RhZ2VzIG9mIHRoZWlyIGNhcmVlcnMsIGNoYWxsZW5naW5nIHdlbGwta25vd24gcHJlY29uY2VwdGlvbnMgYW5kIGV4cGxvcmluZyBuZXcgcGVyc3BlY3RpdmVzLjwvZGVzY3JpcHRpb24+CiAgICA8ZGVzY3JpcHRpb24gZGVzY3JpcHRpb25UeXBlPSJPdGhlciI+MTcgcGFnZXMsIDEgZmlndXJlPC9kZXNjcmlwdGlvbj4KICA8L2Rlc2NyaXB0aW9ucz4KPC9yZXNvdXJjZT4=","url":"https://arxiv.org/abs/2207.04779","contentUrl":null,"metadataVersion":1,"schemaVersion":"http://datacite.org/schema/kernel-4","source":"mds","isActive":true,"state":"findable","reason":null,"viewCount":0,"viewsOverTime":[],"downloadCount":0,"downloadsOverTime":[],"referenceCount":0,"citationCount":0,"citationsOverTime":[],"partCount":0,"partOfCount":0,"versionCount":0,"versionOfCount":0,"created":"2022-07-12T01:41:56.000Z","registered":"2022-07-12T01:41:57.000Z","published":"2022","updated":"2022-07-13T01:24:20.000Z"},"relationships":{"client":{"data":{"id":"arxiv.content","type":"clients"}},"provider":{"data":{"id":"arxiv","type":"providers"}},"media":{"data":{"id":"10.48550/arxiv.2207.04779","type":"media"}},"references":{"data":[]},"citations":{"data":[]},"parts":{"data":[]},"partOf":{"data":[]},"versions":{"data":[]},"versionOf":{"data":[]}}}} diff --git a/dspace/config/spring/api/datacite-integration.xml b/dspace/config/spring/api/datacite-integration.xml index e1efa3eadbd9..609f49d111ed 100644 --- a/dspace/config/spring/api/datacite-integration.xml +++ b/dspace/config/spring/api/datacite-integration.xml @@ -24,8 +24,13 @@ + + + + + + - From 607e7458e02fa832b18eac91728944bf9e013bb5 Mon Sep 17 00:00:00 2001 From: Johanna Staudinger Date: Mon, 12 Dec 2022 14:43:48 +0100 Subject: [PATCH 123/394] Minor fixes: remove unused class field avoid potential NPE on invalid JSON --- .../datacite/DataCiteImportMetadataSourceServiceImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java index 59214ac1176e..f19a406736b8 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java @@ -14,7 +14,6 @@ import java.util.List; import java.util.Map; import javax.el.MethodNotFoundException; -import javax.ws.rs.client.WebTarget; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -54,8 +53,6 @@ public class DataCiteImportMetadataSourceServiceImpl private final String url = "https://api.datacite.org/dois/"; - private WebTarget webTarget; - @Override public String getImportSource() { return "datacite"; @@ -100,6 +97,10 @@ public Collection getRecords(String query, int start, int count) t uriParameters.put("query", id); String responseString = liveImportClient.executeHttpGetRequest(timeoutMs, url, params); JsonNode jsonNode = convertStringJsonToJsonNode(responseString); + if (jsonNode == null) { + log.warn("DataCite returned invalid JSON"); + return records; + } JsonNode dataNode = jsonNode.at("/data"); if (dataNode.isArray()) { Iterator iterator = dataNode.iterator(); From f0cd84ed7782105dd8941d713dcdd06f02b1030f Mon Sep 17 00:00:00 2001 From: Philipp Rumpf Date: Mon, 12 Dec 2022 14:19:02 +0000 Subject: [PATCH 124/394] Tests: configure dc.contributor.author, test it --- ...DataCiteImportMetadataSourceServiceIT.java | 21 ++++++++++++++++++- .../spring/api/datacite-integration.xml | 14 +++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java index 466b14a863b9..83ebc40c7966 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DataCiteImportMetadataSourceServiceIT.java @@ -117,9 +117,28 @@ private ArrayList getRecords() { MetadatumDTO title = createMetadatumDTO("dc", "title", null, "Mathematical Proof Between Generations"); MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.48550/arxiv.2207.04779"); - + MetadatumDTO author1 = createMetadatumDTO("dc", "contributor", "author", "Bayer, Jonas"); + MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Benzmüller, Christoph"); + MetadatumDTO author3 = createMetadatumDTO("dc", "contributor", "author", "Buzzard, Kevin"); + MetadatumDTO author4 = createMetadatumDTO("dc", "contributor", "author", "David, Marco"); + MetadatumDTO author5 = createMetadatumDTO("dc", "contributor", "author", "Lamport, Leslie"); + MetadatumDTO author6 = createMetadatumDTO("dc", "contributor", "author", "Matiyasevich, Yuri"); + MetadatumDTO author7 = createMetadatumDTO("dc", "contributor", "author", "Paulson, Lawrence"); + MetadatumDTO author8 = createMetadatumDTO("dc", "contributor", "author", "Schleicher, Dierk"); + MetadatumDTO author9 = createMetadatumDTO("dc", "contributor", "author", "Stock, Benedikt"); + MetadatumDTO author10 = createMetadatumDTO("dc", "contributor", "author", "Zelmanov, Efim"); metadatums.add(title); metadatums.add(doi); + metadatums.add(author1); + metadatums.add(author2); + metadatums.add(author3); + metadatums.add(author4); + metadatums.add(author5); + metadatums.add(author6); + metadatums.add(author7); + metadatums.add(author8); + metadatums.add(author9); + metadatums.add(author10); ImportRecord firstRecord = new ImportRecord(metadatums); diff --git a/dspace/config/spring/api/datacite-integration.xml b/dspace/config/spring/api/datacite-integration.xml index 609f49d111ed..236ec0a3bda9 100644 --- a/dspace/config/spring/api/datacite-integration.xml +++ b/dspace/config/spring/api/datacite-integration.xml @@ -20,9 +20,23 @@ + + + + + + + + + + + + + + From fc6ac8e078ec66caa76c96dd130a1f04d1d322d4 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 13 Dec 2022 12:00:00 +0100 Subject: [PATCH 125/394] 97080: #1712 - Refactoring facet search all words --- .../org/dspace/discovery/DiscoverResult.java | 5 +++ .../org/dspace/discovery/SolrServiceImpl.java | 35 +++++++++++++++++-- .../indexobject/ItemIndexFactoryImpl.java | 8 +++-- dspace/solr/search/conf/schema.xml | 1 + 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java index b2bd0fc5ff35..00236d2bfe32 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java +++ b/dspace-api/src/main/java/org/dspace/discovery/DiscoverResult.java @@ -7,6 +7,8 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -109,6 +111,9 @@ public List getFacetResult(DiscoverySearchFilterFacet field) { if (facetValues.size() == 0 && field.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { facetValues = getFacetResult(field.getIndexFieldName() + ".year"); } + if (facetValues.isEmpty()) { + facetValues = getFacetResult(field.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES); + } return ListUtils.emptyIfNull(facetValues); } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index d64451def341..3ca9e38fec72 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -103,6 +103,8 @@ public class SolrServiceImpl implements SearchService, IndexingService { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrServiceImpl.class); + public static final String SOLR_FIELD_SUFFIX_FACET_PREFIXES = "_prefix"; + @Autowired protected ContentServiceFactory contentServiceFactory; @Autowired @@ -805,6 +807,9 @@ protected SolrQuery resolveToSolrQuery(Context context, DiscoverQuery discoveryQ //Only add facet information if there are any facets for (DiscoverFacetField facetFieldConfig : facetFields) { String field = transformFacetField(facetFieldConfig, facetFieldConfig.getField(), false); + if (facetFieldConfig.getPrefix() != null) { + field = transformPrefixFacetField(facetFieldConfig, facetFieldConfig.getField(), false); + } solrQuery.addFacetField(field); // Setting the facet limit in this fashion ensures that each facet can have its own max @@ -1247,12 +1252,36 @@ public String toSortFieldIndex(String metadataField, String type) { } } + /** + * Gets the solr field that contains the the facet value split on each word break to the end, so can be searched + * on each word in the value, see {@link org.dspace.discovery.indexobject.ItemIndexFactoryImpl + * #saveFacetPrefixParts(SolrInputDocument, DiscoverySearchFilter, String, String)} + * Ony applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular + * facet filter field + */ + protected String transformPrefixFacetField(DiscoverFacetField facetFieldConfig, String field, + boolean removePostfix) { + if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT) || + facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) { + if (removePostfix) { + return field.substring(0, field.lastIndexOf(SOLR_FIELD_SUFFIX_FACET_PREFIXES)); + } else { + return field + SOLR_FIELD_SUFFIX_FACET_PREFIXES; + } + } else { + return this.transformFacetField(facetFieldConfig, field, removePostfix); + } + } + protected String transformFacetField(DiscoverFacetField facetFieldConfig, String field, boolean removePostfix) { + if (field.contains(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) { + return this.transformPrefixFacetField(facetFieldConfig, field, removePostfix); + } if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) { if (removePostfix) { - return field.substring(0, field.lastIndexOf("_prefix")); + return field.substring(0, field.lastIndexOf("_filter")); } else { - return field + "_prefix"; + return field + "_filter"; } } else if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { if (removePostfix) { @@ -1293,7 +1322,7 @@ protected String transformDisplayedValue(Context context, String field, String v if (field.equals("location.comm") || field.equals("location.coll")) { value = locationToName(context, field, value); } else if (field.endsWith("_filter") || field.endsWith("_ac") - || field.endsWith("_acid")) { + || field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) { //We have a filter make sure we split ! String separator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("discovery.solr.facets.split.char"); diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 85c3939ac61a..e90de0ddc0bc 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -7,6 +7,8 @@ */ package org.dspace.discovery.indexobject; +import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES; + import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; @@ -759,12 +761,14 @@ private void indexIfFilterTypeFacet(SolrInputDocument doc, DiscoverySearchFilter doc.addField(searchFilter.getIndexFieldName() + "_keyword", indexValue); } } + //Also add prefix field with all parts of value + saveFacetPrefixParts(doc, searchFilter, value, separator); } } /** * Stores every "value part" in lowercase, together with the original value in regular case, - * separated by the separator, in the {fieldName}_prefix field. + * separated by the separator, in the {fieldName}{@link SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES} field. *
* E.g. Author "With Multiple Words" gets stored as: *
@@ -787,7 +791,7 @@ private void saveFacetPrefixParts(SolrInputDocument doc, DiscoverySearchFilter s while (matcher.find()) { int index = matcher.start(); String currentPart = StringUtils.substring(value, index); - doc.addField(searchFilter.getIndexFieldName() + "_prefix", + doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, currentPart.toLowerCase() + separator + value); } } diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index 09eeb173e208..1a3ac454a428 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -293,6 +293,7 @@ + From 7c68e09486430e0c23a215a7a6315331acc765aa Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 13 Dec 2022 14:44:04 -0600 Subject: [PATCH 126/394] Add database skip command. Minor refactor of DatabaseUtils to use switch --- .../dspace/storage/rdbms/DatabaseUtils.java | 668 +++++++++++------- 1 file changed, 429 insertions(+), 239 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java index 1a690afd8686..1464fb44ecda 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java @@ -26,6 +26,7 @@ import javax.sql.DataSource; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; @@ -37,6 +38,7 @@ import org.flywaydb.core.Flyway; import org.flywaydb.core.api.FlywayException; import org.flywaydb.core.api.MigrationInfo; +import org.flywaydb.core.api.MigrationVersion; import org.flywaydb.core.api.callback.Callback; import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.internal.info.MigrationInfoDumper; @@ -93,7 +95,7 @@ public static void main(String[] argv) { // Usage checks if (argv.length < 1) { System.out.println("\nDatabase action argument is missing."); - System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'validate', " + + System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'skip', 'validate', " + "'update-sequences' or 'clean'"); System.out.println("\nOr, type 'database help' for more information.\n"); System.exit(1); @@ -111,280 +113,339 @@ public static void main(String[] argv) { // *before* any other Flyway commands can be run. This is a safety check. FlywayUpgradeUtils.upgradeFlywayTable(flyway, dataSource.getConnection()); - // "test" = Test Database Connection - if (argv[0].equalsIgnoreCase("test")) { - // Try to connect to the database - System.out.println("\nAttempting to connect to database"); - try (Connection connection = dataSource.getConnection()) { - System.out.println("Connected successfully!"); + // Determine action param passed to "./dspace database" + switch (argv[0].toLowerCase(Locale.ENGLISH)) { + // "test" = Test Database Connection + case "test": + // Try to connect to the database + System.out.println("\nAttempting to connect to database"); + try (Connection connection = dataSource.getConnection()) { + System.out.println("Connected successfully!"); - // Print basic database connection information - printDBInfo(connection); + // Print basic database connection information + printDBInfo(connection); - // Print any database warnings/errors found (if any) - boolean issueFound = printDBIssues(connection); + // Print any database warnings/errors found (if any) + boolean issueFound = printDBIssues(connection); - // If issues found, exit with an error status (even if connection succeeded). - if (issueFound) { + // If issues found, exit with an error status (even if connection succeeded). + if (issueFound) { + System.exit(1); + } else { + System.exit(0); + } + } catch (SQLException sqle) { + System.err.println("\nError running 'test': "); + System.err.println(" - " + sqle); + System.err.println("\nPlease see the DSpace documentation for assistance.\n"); + sqle.printStackTrace(System.err); System.exit(1); - } else { - System.exit(0); } - } catch (SQLException sqle) { - System.err.println("\nError running 'test': "); - System.err.println(" - " + sqle); - System.err.println("\nPlease see the DSpace documentation for assistance.\n"); - sqle.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("info") || argv[0].equalsIgnoreCase("status")) { - try (Connection connection = dataSource.getConnection()) { - // Print basic Database info - printDBInfo(connection); - - // Get info table from Flyway - System.out.println("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all())); - - // If Flyway is NOT yet initialized, also print the determined version information - // NOTE: search is case sensitive, as flyway table name is ALWAYS lowercase, - // See: http://flywaydb.org/documentation/faq.html#case-sensitive - if (!tableExists(connection, flyway.getConfiguration().getTable(), true)) { - System.out - .println("\nNOTE: This database is NOT yet initialized for auto-migrations (via Flyway)."); - // Determine which version of DSpace this looks like - String dbVersion = determineDBVersion(connection); - if (dbVersion != null) { + break; + // "info" and "status" are identical and provide database info + case "info": + case "status": + try (Connection connection = dataSource.getConnection()) { + // Print basic Database info + printDBInfo(connection); + + // Get info table from Flyway + System.out.println("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all())); + + // If Flyway is NOT yet initialized, also print the determined version information + // NOTE: search is case sensitive, as flyway table name is ALWAYS lowercase, + // See: http://flywaydb.org/documentation/faq.html#case-sensitive + if (!tableExists(connection, flyway.getConfiguration().getTable(), true)) { System.out - .println("\nYour database looks to be compatible with DSpace version " + dbVersion); - System.out.println( - "All upgrades *after* version " + dbVersion + " will be run during the next migration" + - "."); - System.out.println("\nIf you'd like to upgrade now, simply run 'dspace database migrate'."); + .println("\nNOTE: This database is NOT yet initialized for auto-migrations " + + "(via Flyway)."); + // Determine which version of DSpace this looks like + String dbVersion = determineDBVersion(connection); + if (dbVersion != null) { + System.out + .println("\nYour database looks to be compatible with DSpace version " + dbVersion); + System.out.println( + "All upgrades *after* version " + dbVersion + " will be run during the next " + + "migration."); + System.out.println("\nIf you'd like to upgrade now, simply run 'dspace database " + + "migrate'."); + } } - } - // Print any database warnings/errors found (if any) - boolean issueFound = printDBIssues(connection); + // Print any database warnings/errors found (if any) + boolean issueFound = printDBIssues(connection); - // If issues found, exit with an error status - if (issueFound) { + // If issues found, exit with an error status + if (issueFound) { + System.exit(1); + } else { + System.exit(0); + } + } catch (SQLException e) { + System.err.println("Info exception:"); + e.printStackTrace(System.err); System.exit(1); - } else { - System.exit(0); } - } catch (SQLException e) { - System.err.println("Info exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("migrate")) { - try (Connection connection = dataSource.getConnection()) { - System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); - - // "migrate" allows for an OPTIONAL second argument (only one may be specified): - // - "ignored" = Also run any previously "ignored" migrations during the migration - // - "force" = Even if no pending migrations exist, still run a migration to trigger callbacks. - // - [version] = ONLY run migrations up to a specific DSpace version (ONLY FOR TESTING) - if (argv.length == 2) { - if (argv[1].equalsIgnoreCase("ignored")) { - System.out.println( - "Migrating database to latest version AND running previously \"Ignored\" " + - "migrations... (Check logs for details)"); - // Update the database to latest version, but set "outOfOrder=true" - // This will ensure any old migrations in the "ignored" state are now run - updateDatabase(dataSource, connection, null, true); - } else if (argv[1].equalsIgnoreCase("force")) { - updateDatabase(dataSource, connection, null, false, true); + break; + // "migrate" = Run all pending database migrations + case "migrate": + try (Connection connection = dataSource.getConnection()) { + System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); + + // "migrate" allows for an OPTIONAL second argument (only one may be specified): + // - "ignored" = Also run any previously "ignored" migrations during the migration + // - "force" = Even if no pending migrations exist, still run migrate to trigger callbacks. + // - [version] = ONLY run migrations up to a specific DSpace version (ONLY FOR TESTING) + if (argv.length == 2) { + if (argv[1].equalsIgnoreCase("ignored")) { + System.out.println( + "Migrating database to latest version AND running previously \"Ignored\" " + + "migrations... (Check logs for details)"); + // Update the database to latest version, but set "outOfOrder=true" + // This will ensure any old migrations in the "ignored" state are now run + updateDatabase(dataSource, connection, null, true); + } else if (argv[1].equalsIgnoreCase("force")) { + updateDatabase(dataSource, connection, null, false, true); + } else { + // Otherwise, we assume "argv[1]" is a valid migration version number + // This is only for testing! Never specify for Production! + String migrationVersion = argv[1]; + BufferedReader input = new BufferedReader( + new InputStreamReader(System.in, StandardCharsets.UTF_8)); + + System.out.println( + "You've specified to migrate your database ONLY to version " + migrationVersion + + " ..."); + System.out.println( + "\nWARNING: In this mode, we DISABLE all callbacks, which means that you will " + + "need to manually update registries and manually run a reindex. This is " + + "because you are attempting to use an OLD version (" + migrationVersion + ") " + + "Database with a newer DSpace API. NEVER do this in a PRODUCTION scenario. " + + "The resulting database is only useful for migration testing.\n"); + + System.out.print( + "Are you SURE you only want to migrate your database to version " + + migrationVersion + "? [y/n]: "); + String choiceString = input.readLine(); + input.close(); + + if (choiceString.equalsIgnoreCase("y")) { + System.out.println( + "Migrating database ONLY to version " + migrationVersion + " ... " + + "(Check logs for details)"); + // Update the database, to the version specified. + updateDatabase(dataSource, connection, migrationVersion, false); + } else { + System.out.println("No action performed."); + } + } } else { - // Otherwise, we assume "argv[1]" is a valid migration version number - // This is only for testing! Never specify for Production! + System.out.println("Migrating database to latest version... " + + "(Check dspace logs for details)"); + updateDatabase(dataSource, connection); + } + System.out.println("Done."); + System.exit(0); + } catch (SQLException e) { + System.err.println("Migration exception:"); + e.printStackTrace(System.err); + System.exit(1); + } + break; + // "repair" = Run Flyway repair script + case "repair": + try (Connection connection = dataSource.getConnection();) { + System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); + System.out.println( + "Attempting to repair any previously failed migrations (or mismatched checksums) via " + + "FlywayDB... (Check dspace logs for details)"); + flyway.repair(); + System.out.println("Done."); + System.exit(0); + } catch (SQLException | FlywayException e) { + System.err.println("Repair exception:"); + e.printStackTrace(System.err); + System.exit(1); + } + break; + // "skip" = Skip a specific Flyway migration (by telling Flyway it succeeded) + case "skip": + try { + // "skip" requires a migration version to skip. Only that exact version will be skipped. + if (argv.length == 2) { String migrationVersion = argv[1]; - BufferedReader input = new BufferedReader( - new InputStreamReader(System.in, StandardCharsets.UTF_8)); + BufferedReader input = new BufferedReader( + new InputStreamReader(System.in, StandardCharsets.UTF_8)); System.out.println( - "You've specified to migrate your database ONLY to version " + migrationVersion + " " + + "You've specified to SKIP the migration with version='" + migrationVersion + "' " + "..."); - System.out.println( - "\nWARNING: In this mode, we DISABLE all callbacks, which means that you will need " + - "to manually update registries and manually run a reindex. This is because you " + - "are attempting to use an OLD version (" + migrationVersion + ") Database with " + - "a newer DSpace API. NEVER do this in a PRODUCTION scenario. The resulting " + - "database is only useful for migration testing.\n"); - System.out.print( - "Are you SURE you only want to migrate your database to version " + migrationVersion - + "? [y/n]: "); + "\nWARNING: You should only skip migrations which are no longer required or have " + + "become obsolete. Skipping a REQUIRED migration may result in DSpace failing " + + "to startup or function properly. Are you sure you want to SKIP the " + + "migration with version '" + migrationVersion + "'? [y/n]: "); String choiceString = input.readLine(); input.close(); if (choiceString.equalsIgnoreCase("y")) { System.out.println( - "Migrating database ONLY to version " + migrationVersion + " ... (Check logs for " + - "details)"); - // Update the database, to the version specified. - updateDatabase(dataSource, connection, migrationVersion, false); - } else { - System.out.println("No action performed."); + "Attempting to skip migration with version " + migrationVersion + " " + + "... (Check logs for details)"); + skipMigration(dataSource, migrationVersion); } + } else { + System.out.println("The 'skip' command REQUIRES a version to be specified. " + + "Only that single migration will be skipped. For the list " + + "of migration versions use the 'info' command."); } - } else { - System.out.println("Migrating database to latest version... (Check dspace logs for details)"); - updateDatabase(dataSource, connection); + } catch (IOException e) { + System.err.println("Exception when attempting to skip migration:"); + e.printStackTrace(System.err); + System.exit(1); } - System.out.println("Done."); - System.exit(0); - } catch (SQLException e) { - System.err.println("Migration exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("repair")) { - // "repair" = Run Flyway repair script - - try (Connection connection = dataSource.getConnection();) { - System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); - System.out.println( - "Attempting to repair any previously failed migrations (or mismatched checksums) via " + - "FlywayDB... (Check dspace logs for details)"); - flyway.repair(); - System.out.println("Done."); - System.exit(0); - } catch (SQLException | FlywayException e) { - System.err.println("Repair exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("validate")) { + break; // "validate" = Run Flyway validation to check for database errors/issues - - try (Connection connection = dataSource.getConnection();) { - System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); - System.out - .println("Attempting to validate database status (and migration checksums) via FlywayDB..."); - flyway.validate(); - System.out.println("No errors thrown. Validation succeeded. (Check dspace logs for more details)"); - System.exit(0); - } catch (SQLException | FlywayException e) { - System.err.println("Validation exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("clean")) { + case "validate": + try (Connection connection = dataSource.getConnection();) { + System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); + System.out + .println("Attempting to validate database status (and migration checksums) via " + + "FlywayDB..."); + flyway.validate(); + System.out.println("No errors thrown. Validation succeeded. (Check dspace logs for more " + + "details)"); + System.exit(0); + } catch (SQLException | FlywayException e) { + System.err.println("Validation exception:"); + e.printStackTrace(System.err); + System.exit(1); + } + break; // "clean" = Run Flyway clean script + case "clean": + // If clean is disabled, return immediately + if (flyway.getConfiguration().isCleanDisabled()) { + System.out.println( + "\nWARNING: 'clean' command is currently disabled, as it is dangerous to run in " + + "Production scenarios!"); + System.out.println( + "\nIn order to run a 'clean' you first must enable it in your DSpace config by " + + "specifying 'db.cleanDisabled=false'.\n"); + System.exit(1); + } - // If clean is disabled, return immediately - if (flyway.getConfiguration().isCleanDisabled()) { - System.out.println( - "\nWARNING: 'clean' command is currently disabled, as it is dangerous to run in Production " + - "scenarios!"); - System.out.println( - "\nIn order to run a 'clean' you first must enable it in your DSpace config by specifying 'db" + - ".cleanDisabled=false'.\n"); - System.exit(1); - } - - try (Connection connection = dataSource.getConnection()) { - String dbType = getDbType(connection); + try (Connection connection = dataSource.getConnection()) { + String dbType = getDbType(connection); - // Not all Postgres user accounts will be able to run a 'clean', - // as only 'superuser' accounts can remove the 'pgcrypto' extension. - if (dbType.equals(DBMS_POSTGRES)) { - // Check if database user has permissions suitable to run a clean - if (!PostgresUtils.checkCleanPermissions(connection)) { - String username = connection.getMetaData().getUserName(); - // Exit immediately, providing a descriptive error message - System.out.println( - "\nERROR: The database user '" + username + "' does not have sufficient privileges to" + - " run a 'database clean' (via Flyway)."); - System.out.println( - "\nIn order to run a 'clean', the database user MUST have 'superuser' privileges"); - System.out.println( - "OR the '" + PostgresUtils.PGCRYPTO + "' extension must be installed in a separate " + - "schema (see documentation)."); - System.out.println( - "\nOptionally, you could also manually remove the '" + PostgresUtils.PGCRYPTO + "' " + - "extension first (DROP EXTENSION " + PostgresUtils.PGCRYPTO + " CASCADE;), then " + - "rerun the 'clean'"); - System.exit(1); + // Not all Postgres user accounts will be able to run a 'clean', + // as only 'superuser' accounts can remove the 'pgcrypto' extension. + if (dbType.equals(DBMS_POSTGRES)) { + // Check if database user has permissions suitable to run a clean + if (!PostgresUtils.checkCleanPermissions(connection)) { + String username = connection.getMetaData().getUserName(); + // Exit immediately, providing a descriptive error message + System.out.println( + "\nERROR: The database user '" + username + "' does not have sufficient " + + "privileges to run a 'database clean' (via Flyway)."); + System.out.println( + "\nIn order to run a 'clean', the database user MUST have 'superuser' privileges"); + System.out.println( + "OR the '" + PostgresUtils.PGCRYPTO + "' extension must be installed in a " + + "separate schema (see documentation)."); + System.out.println( + "\nOptionally, you could also manually remove the '" + PostgresUtils.PGCRYPTO + + "' extension first (DROP EXTENSION " + PostgresUtils.PGCRYPTO + + " CASCADE;), then rerun the 'clean'"); + System.exit(1); + } } - } - - BufferedReader input = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); - System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); - System.out - .println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n"); - System.out.println("There is NO turning back from this action. Backup your DB before continuing."); - if (dbType.equals(DBMS_ORACLE)) { - System.out.println("\nORACLE WARNING: your RECYCLEBIN will also be PURGED.\n"); - } else if (dbType.equals(DBMS_POSTGRES)) { - System.out.println( - "\nPOSTGRES WARNING: the '" + PostgresUtils.PGCRYPTO + "' extension will be dropped if it" + - " is in the same schema as the DSpace database.\n"); - } - System.out.print("Do you want to PERMANENTLY DELETE everything from your database? [y/n]: "); - String choiceString = input.readLine(); - input.close(); + BufferedReader input = new BufferedReader(new InputStreamReader(System.in, + StandardCharsets.UTF_8)); - if (choiceString.equalsIgnoreCase("y")) { - System.out.println("Scrubbing database clean... (Check dspace logs for details)"); - cleanDatabase(flyway, dataSource); - System.out.println("Done."); - System.exit(0); - } else { - System.out.println("No action performed."); + System.out.println("\nDatabase URL: " + connection.getMetaData().getURL()); + System.out + .println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n"); + System.out.println("There is NO turning back from this action. Backup your DB before " + + "continuing."); + if (dbType.equals(DBMS_ORACLE)) { + System.out.println("\nORACLE WARNING: your RECYCLEBIN will also be PURGED.\n"); + } else if (dbType.equals(DBMS_POSTGRES)) { + System.out.println( + "\nPOSTGRES WARNING: the '" + PostgresUtils.PGCRYPTO + "' extension will be dropped " + + "if it is in the same schema as the DSpace database.\n"); + } + System.out.print("Do you want to PERMANENTLY DELETE everything from your database? [y/n]: "); + String choiceString = input.readLine(); + input.close(); + + if (choiceString.equalsIgnoreCase("y")) { + System.out.println("Scrubbing database clean... (Check dspace logs for details)"); + cleanDatabase(flyway, dataSource); + System.out.println("Done."); + System.exit(0); + } else { + System.out.println("No action performed."); + } + } catch (SQLException e) { + System.err.println("Clean exception:"); + e.printStackTrace(System.err); + System.exit(1); } - } catch (SQLException e) { - System.err.println("Clean exception:"); - e.printStackTrace(System.err); - System.exit(1); - } - } else if (argv[0].equalsIgnoreCase("update-sequences")) { - try (Connection connection = dataSource.getConnection()) { - String dbType = getDbType(connection); - String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType + - "/update-sequences.sql"; - InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile); - if (sqlstream != null) { - String s = IOUtils.toString(sqlstream, "UTF-8"); - if (!s.isEmpty()) { - System.out.println("Running " + sqlfile); - connection.createStatement().execute(s); - System.out.println("update-sequences complete"); + break; + // "update-sequences" = Run DSpace's "update-sequences.sql" script + case "update-sequences": + try (Connection connection = dataSource.getConnection()) { + String dbType = getDbType(connection); + String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType + + "/update-sequences.sql"; + InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile); + if (sqlstream != null) { + String s = IOUtils.toString(sqlstream, StandardCharsets.UTF_8); + if (!s.isEmpty()) { + System.out.println("Running " + sqlfile); + connection.createStatement().execute(s); + System.out.println("update-sequences complete"); + } else { + System.err.println(sqlfile + " contains no SQL to execute"); + } } else { - System.err.println(sqlfile + " contains no SQL to execute"); + System.err.println(sqlfile + " not found"); } - } else { - System.err.println(sqlfile + " not found"); } - } - } else { - System.out.println("\nUsage: database [action]"); - System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', " + - "'update-sequences' or 'clean'"); - System.out.println( - " - test = Performs a test connection to database to " + - "validate connection settings"); - System.out.println( - " - info / status = Describe basic info/status about database, including validating the " + - "compatibility of this database"); - System.out.println( - " - migrate = Migrate the database to the latest version"); - System.out.println( - " - repair = Attempt to repair any previously failed database " + - "migrations or checksum mismatches (via Flyway repair)"); - System.out.println( - " - validate = Validate current database's migration status (via Flyway validate), " + - "validating all migration checksums."); - System.out.println( - " - update-sequences = Update database sequences after running AIP ingest."); - System.out.println( - " - clean = DESTROY all data and tables in database " + - "(WARNING there is no going back!). " + - "Requires 'db.cleanDisabled=false' setting in config."); - System.out.println(""); - System.exit(0); + break; + // default = show help information + default: + System.out.println("\nUsage: database [action]"); + System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'skip', " + + "'validate', 'update-sequences' or 'clean'"); + System.out.println( + " - test = Performs a test connection to database to " + + "validate connection settings"); + System.out.println( + " - info / status = Describe basic info/status about database, including validating the " + + "compatibility of this database"); + System.out.println( + " - migrate = Migrate the database to the latest version"); + System.out.println( + " - repair = Attempt to repair any previously failed database " + + "migrations or checksum mismatches (via Flyway repair)"); + System.out.println( + " - skip [version] = Skip a single, pending or ignored migration, " + + "ensuring it never runs."); + System.out.println( + " - validate = Validate current database's migration status (via Flyway validate), " + + "validating all migration checksums."); + System.out.println( + " - update-sequences = Update database sequences after running AIP ingest."); + System.out.println( + " - clean = DESTROY all data and tables in database " + + "(WARNING there is no going back!). " + + "Requires 'db.cleanDisabled=false' setting in config."); + System.out.println(""); + System.exit(0); + break; } } catch (Exception e) { @@ -786,6 +847,89 @@ protected static synchronized void updateDatabase(DataSource datasource, Connect } } + /** + * Skips the given migration by marking it as "successful" in the Flyway table. This ensures + * the given migration will never be run again. + *

+ * WARNING: Skipping a required migration can result in unexpected errors. Make sure the migration is + * not required (or obsolete) before skipping it. + * @param dataSource current DataSource + * @param skipVersion version of migration to skip + * @throws SQLException if error occurs + */ + private static synchronized void skipMigration(DataSource dataSource, + String skipVersion) throws SQLException { + if (null == dataSource) { + throw new SQLException("The datasource is a null reference -- cannot continue."); + } + + try (Connection connection = dataSource.getConnection()) { + // Setup Flyway API against our database + FluentConfiguration flywayConfiguration = setupFlyway(dataSource); + + // In order to allow for skipping "Ignored" migrations, we MUST set "outOfOrder=true". + // (Otherwise Ignored migrations never appear in the pending list) + flywayConfiguration.outOfOrder(true); + + // Initialized Flyway object based on this configuration + Flyway flyway = flywayConfiguration.load(); + + // Find the migration we are skipping in the list of pending migrations + boolean foundMigration = false; + for (MigrationInfo migration : flyway.info().pending()) { + // If this migration matches our "skipVersion" + if (migration.getVersion().equals(MigrationVersion.fromVersion(skipVersion))) { + foundMigration = true; + System.out.println("Found migration matching version='" + skipVersion + "'. " + + "Changing state to 'Success' in order to skip it."); + + PreparedStatement statement = null; + try { + // Create SQL Insert which will log this migration as having already been run. + String INSERT_SQL = "INSERT INTO " + FLYWAY_TABLE + " " + + "(" + + "installed_rank, version, description, type, script, " + + "checksum, installed_by, execution_time, success" + + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + statement = connection.prepareStatement(INSERT_SQL); + // installed_rank + statement.setInt(1, getNextFlywayInstalledRank(flyway)); + // version + statement.setString(2, migration.getVersion().getVersion()); + // description + statement.setString(3, migration.getDescription()); + // type + statement.setString(4, migration.getType().toString()); + // script + statement.setString(5, migration.getScript()); + // checksum + statement.setInt(6, migration.getChecksum()); + // installed_by + statement.setString(7, getDBUserName(connection)); + // execution_time is set to zero as we didn't really execute it + statement.setInt(8, 0); + // success=true tells Flyway this migration no longer needs to be run. + statement.setBoolean(9, true); + + // Run the INSERT + statement.executeUpdate(); + } finally { + if (statement != null && !statement.isClosed()) { + statement.close(); + } + } + } + } + if (!foundMigration) { + System.err.println("Could not find migration to skip! " + + "No 'Pending' or 'Ignored' migrations match version='" + skipVersion + "'"); + } + } catch (FlywayException fe) { + // If any FlywayException (Runtime) is thrown, change it to a SQLException + throw new SQLException("Flyway error occurred", fe); + } + } + /** * Clean the existing database, permanently removing all data and tables *

@@ -1192,6 +1336,34 @@ public static String getSchemaName(Connection connection) return schema; } + /** + * Get the Database User Name in use by this Connection. + * + * @param connection Current Database Connection + * @return User name as a string, or "null" if cannot be determined or unspecified + * @throws SQLException An exception that provides information on a database access error or other errors. + */ + public static String getDBUserName(Connection connection) + throws SQLException { + String username = null; + + // Try to get the schema from the DB connection itself. + // As long as the Database driver supports JDBC4.1, there should be a getSchema() method + // If this method is unimplemented or doesn't exist, it will throw an exception (likely an AbstractMethodError) + try { + username = connection.getMetaData().getUserName(); + } catch (Exception | AbstractMethodError e) { + // ignore + } + + // If we don't know our schema, let's try the schema in the DSpace configuration + if (StringUtils.isBlank(username)) { + username = canonicalize(connection, DSpaceServicesFactory.getInstance().getConfigurationService() + .getProperty("db.username")); + } + return username; + } + /** * Return the canonical name for a database identifier based on whether this * database defaults to storing identifiers in uppercase or lowercase. @@ -1443,4 +1615,22 @@ public static Double getCurrentFlywayDSpaceState(Connection connection) throws S } return null; } + + /** + * Determine next valid "installed_rank" value from Flyway, based on the "installed_rank" of the + * last applied migration. + * @param flyway currently loaded Flyway + * @return next installed rank value + */ + private static int getNextFlywayInstalledRank(Flyway flyway) throws FlywayException { + // Load all applied migrations + MigrationInfo[] appliedMigrations = flyway.info().applied(); + // If no applied migrations, throw an error. + // This should never happen, but this would mean Flyway is not installed or initialized + if (ArrayUtils.isEmpty(appliedMigrations)) { + throw new FlywayException("Cannot determine next 'installed_rank' as no applied migrations exist"); + } + // Find the last migration in the list, and increment its "installed_rank" by one. + return appliedMigrations[appliedMigrations.length - 1].getInstalledRank() + 1; + } } From 25b8ba8ead86f27cecf668d37dda35bc020f423f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 14 Dec 2022 11:10:36 +0100 Subject: [PATCH 127/394] [CST-7756] added tests --- .../rest/SubscriptionRestRepositoryIT.java | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index 15d57b33765a..f5975109478e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.SubscriptionMatcher; import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.SubscriptionRest; @@ -696,4 +697,166 @@ public void putSubscriptionAdminTest() throws Exception { .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))); } + @Test + public void linkedEpersonOfSubscriptionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/ePerson")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(EPersonMatcher.matchEPersonEntry(eperson)))); + } + + @Test + public void linkedEpersonOfSubscriptionTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/ePerson")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is(EPersonMatcher.matchEPersonEntry(eperson)))); + } + + @Test + public void linkedEpersonOfSubscriptionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/ePerson")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void linkedEpersonOfSubscriptionForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/ePerson")) + .andExpect(status().isForbidden()); + } + + @Test + public void linkedEpersonOfSubscriptionNotFoundTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE + "/ePerson")) + .andExpect(status().isNotFound()); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/dSpaceObject")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(publicItem.getID().toString()))) + .andExpect(jsonPath("$.name", Matchers.is(publicItem.getName()))) + .andExpect(jsonPath("$.withdrawn", Matchers.is(false))) + .andExpect(jsonPath("$.discoverable", Matchers.is(true))) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))) + .andExpect(jsonPath("$.type", Matchers.is("item"))); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(eperson.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/dSpaceObject")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(publicItem.getID().toString()))) + .andExpect(jsonPath("$.name", Matchers.is(publicItem.getName()))) + .andExpect(jsonPath("$.withdrawn", Matchers.is(false))) + .andExpect(jsonPath("$.discoverable", Matchers.is(true))) + .andExpect(jsonPath("$.inArchive", Matchers.is(true))) + .andExpect(jsonPath("$.type", Matchers.is("item"))); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/dSpaceObject")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("Parameter"); + subscriptionParameter.setValue("ValueParameter"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "TestType", publicItem, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/dSpaceObject")) + .andExpect(status().isForbidden()); + } + + @Test + public void linkedDSpaceObjectOfSubscriptionNotFoundTest() throws Exception { + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE + "/dSpaceObject")) + .andExpect(status().isNotFound()); + } + } \ No newline at end of file From 57f917ae0e5d12a9e966ef7c6fcc4ef1c950c595 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 14 Dec 2022 16:14:48 +0100 Subject: [PATCH 128/394] [CST-7756] porting of the script for notification of content subscription --- ...ubscriptionDsoMetadataForEmailCompose.java | 80 ++++++++ .../DiscoveryConfigurationService.java | 19 ++ .../DiscoveryRelatedItemConfiguration.java | 16 ++ .../DiscoverySortFunctionConfiguration.java | 66 ++++++ .../subscriptions/ContentGenerator.java | 94 +++++++++ .../SubscriptionEmailNotification.java | 91 +++++++++ .../SubscriptionEmailNotificationCli.java | 15 ++ ...ailNotificationCliScriptConfiguration.java | 16 ++ ...riptionEmailNotificationConfiguration.java | 62 ++++++ .../SubscriptionEmailNotificationService.java | 144 +++++++++++++ .../CollectionsUpdates.java | 48 +++++ .../CommunityUpdates.java | 48 +++++ .../dSpaceObjectsUpdates/ItemsUpdates.java | 193 ++++++++++++++++++ .../service/DSpaceObjectUpdates.java | 70 +++++++ .../service/SubscriptionGenerator.java | 25 +++ dspace/config/spring/api/scripts.xml | 6 + .../api/subscriptions_email_configuration.xml | 76 +++++++ 17 files changed, 1069 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java create mode 100644 dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCli.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java create mode 100644 dspace/config/spring/api/subscriptions_email_configuration.xml diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java new file mode 100644 index 000000000000..05fda2b97475 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/SubscriptionDsoMetadataForEmailCompose.java @@ -0,0 +1,80 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.crosswalk; + +import static org.dspace.content.Item.ANY; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Creates a String to be sent as email body for subscriptions + * + * @author Alba Aliu + */ +public class SubscriptionDsoMetadataForEmailCompose implements StreamDisseminationCrosswalk { + + private List metadata = new ArrayList<>(); + + @Autowired + private ItemService itemService; + + @Override + public boolean canDisseminate(Context context, DSpaceObject dso) { + return Objects.nonNull(dso) && dso.getType() == Constants.ITEM; + } + + @Override + public void disseminate(Context context, DSpaceObject dso, OutputStream out) throws SQLException { + if (dso.getType() == Constants.ITEM) { + Item item = (Item) dso; + PrintStream printStream = new PrintStream(out); + for (String actualMetadata : metadata) { + String[] splitted = actualMetadata.split("\\."); + String qualifier = null; + if (splitted.length == 1) { + qualifier = splitted[2]; + } + var metadataValue = itemService.getMetadataFirstValue(item, splitted[0], splitted[1], qualifier, ANY); + printStream.print(metadataValue + " "); + } + String itemURL = HandleServiceFactory.getInstance() + .getHandleService() + .resolveToURL(context, item.getHandle()); + printStream.print(itemURL); + printStream.print("\n"); + printStream.close(); + } + } + + @Override + public String getMIMEType() { + return "text/plain"; + } + + public List getMetadata() { + return metadata; + } + + public void setMetadata(List metadata) { + this.metadata = metadata; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java index 636e7ccd2ae4..c02c83ece62b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java @@ -139,4 +139,23 @@ public static void main(String[] args) { } } } + + /** + * Retrieves a list of all DiscoveryConfiguration objects where key starts with prefixConfigurationName + * + * @param prefixConfigurationName string as prefix key + */ + public List getDiscoveryConfigurationWithPrefixName(final String prefixConfigurationName) { + List discoveryConfigurationList = new ArrayList<>(); + if (StringUtils.isNotBlank(prefixConfigurationName)) { + for (String key : map.keySet()) { + if (key.equals(prefixConfigurationName) || key.startsWith(prefixConfigurationName)) { + DiscoveryConfiguration config = map.get(key); + discoveryConfigurationList.add(config); + } + } + } + return discoveryConfigurationList; + } + } diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java new file mode 100644 index 000000000000..6c24a6bac671 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryRelatedItemConfiguration.java @@ -0,0 +1,16 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.discovery.configuration; + +/** + * This class extends {@link DiscoveryConfiguration} and add method for set parameters + * to filter query list + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + */ +public class DiscoveryRelatedItemConfiguration extends DiscoveryConfiguration {} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java new file mode 100644 index 000000000000..7fb020cd560b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoverySortFunctionConfiguration.java @@ -0,0 +1,66 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.discovery.configuration; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * + * Extension of {@link DiscoverySortFieldConfiguration} used to configure sorting + * taking advantage of solr function feature. + * + * Order is evaluated by mean of function parameter value and passed in arguments as input. + * + * @author Corrado Lombardi (corrado.lombardi at 4science.it) + * + */ +public class DiscoverySortFunctionConfiguration extends DiscoverySortFieldConfiguration { + + public static final String SORT_FUNCTION = "sort_function"; + private String function; + private List arguments; + private String id; + + public void setFunction(final String function) { + this.function = function; + } + + public void setArguments(final List arguments) { + this.arguments = arguments; + } + + @Override + public String getType() { + return SORT_FUNCTION; + } + + @Override + public String getMetadataField() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + /** + * Returns the function to be used by solr to sort result + * @param functionArgs variable arguments to be inserted in function + * @return + */ + public String getFunction(final Serializable... functionArgs) { + final String args = String.join(",", Optional.ofNullable(arguments).orElse(Collections.emptyList())); + final String result = function + "(" + args + ")"; + return MessageFormat.format(result, functionArgs); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java new file mode 100644 index 000000000000..0365d6396dd6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -0,0 +1,94 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.subscriptions; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.lang.StringUtils.EMPTY; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import javax.annotation.Resource; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.content.crosswalk.StreamDisseminationCrosswalk; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; +import org.dspace.discovery.IndexableObject; +import org.dspace.eperson.EPerson; +import org.dspace.subscriptions.service.SubscriptionGenerator; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation class of SubscriptionGenerator + * which will handle the logic of sending the emails + * in case of content subscriptions + */ +@SuppressWarnings("rawtypes") +public class ContentGenerator implements SubscriptionGenerator { + + private final Logger log = LogManager.getLogger(ContentGenerator.class); + + @SuppressWarnings("unchecked") + @Resource(name = "entityDissemination") + private Map mapEntityDisseminatorProperty = new HashMap(); + + @Autowired + private ItemService itemService; + + @Override + public void notifyForSubscriptions(Context context, EPerson ePerson, + List indexableComm, + List indexableColl, + List indexableItems) { + try { + if (Objects.nonNull(ePerson)) { + Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscriptions_content")); + email.addRecipient(ePerson.getEmail()); + email.addArgument(generateHtmlBodyMail(context, indexableComm)); + email.addArgument(generateHtmlBodyMail(context, indexableColl)); + email.addArgument(generateHtmlBodyMail(context, indexableItems)); + email.send(); + } + } catch (Exception e) { + // log this email error + log.warn("Cannot email user eperson_id:" + ePerson.getID() + " eperson_email:" + ePerson.getEmail()); + } + } + + private String generateHtmlBodyMail(Context context, List indexableObjects) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write("\n".getBytes(UTF_8)); + if (indexableObjects.size() > 0) { + for (IndexableObject indexableObject : indexableObjects) { + out.write("\n".getBytes(UTF_8)); + Item item = (Item) indexableObject.getIndexedObject(); + var entityType = itemService.getEntityTypeLabel(item); + mapEntityDisseminatorProperty.get(entityType).disseminate(context, item, out); + } + return out.toString(); + } else { + out.write("No items".getBytes(UTF_8)); + } + return out.toString(); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return EMPTY; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java new file mode 100644 index 000000000000..3d576b6f16ef --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java @@ -0,0 +1,91 @@ +/** + * 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.subscriptions; + +import java.sql.SQLException; +import java.util.Objects; +import java.util.UUID; + +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.utils.DSpace; + +/** + * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them + * + * @author alba aliu + */ +public class SubscriptionEmailNotification + extends DSpaceRunnable> { + + private Context context; + private SubscriptionEmailNotificationService subscriptionEmailNotificationService; + + @Override + @SuppressWarnings("unchecked") + public SubscriptionEmailNotificationConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("subscription-send", + SubscriptionEmailNotificationConfiguration.class); + } + + @Override + public void setup() throws ParseException { + this.subscriptionEmailNotificationService = new DSpace().getServiceManager().getServiceByName( + SubscriptionEmailNotificationService.class.getName(), SubscriptionEmailNotificationService.class); + } + + @Override + public void internalRun() throws Exception { + assignCurrentUserInContext(); + assignSpecialGroupsInContext(); + String frequencyOption = commandLine.getOptionValue("f"); + if (StringUtils.isBlank(frequencyOption)) { + throw new IllegalArgumentException("Option frequency f must be set"); + } + if (!frequencyOption.equals("D") && !frequencyOption.equals("M") && !frequencyOption.equals("W")) { + throw new IllegalArgumentException("Option f must be D, M or W"); + } + subscriptionEmailNotificationService.perform(getContext(), handler, "content", frequencyOption); + } + + protected void assignCurrentUserInContext() throws SQLException { + context = new Context(); + UUID uuid = getEpersonIdentifier(); + if (Objects.nonNull(uuid)) { + EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid); + context.setCurrentUser(ePerson); + } + } + + private void assignSpecialGroupsInContext() throws SQLException { + for (UUID uuid : handler.getSpecialGroups()) { + context.setSpecialGroup(uuid); + } + } + + public SubscriptionEmailNotificationService getSubscriptionEmailNotificationService() { + return subscriptionEmailNotificationService; + } + + public void setSubscriptionEmailNotificationService(SubscriptionEmailNotificationService subscriptionNotifService) { + this.subscriptionEmailNotificationService = subscriptionNotifService; + } + + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCli.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCli.java new file mode 100644 index 000000000000..338e7ff0e18b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCli.java @@ -0,0 +1,15 @@ +/** + * 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.subscriptions; + +/** + * Extension of {@link SubscriptionEmailNotification} for CLI. + */ +public class SubscriptionEmailNotificationCli extends SubscriptionEmailNotification { + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java new file mode 100644 index 000000000000..f0eb2fd5c83e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationCliScriptConfiguration.java @@ -0,0 +1,16 @@ +/** + * 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.subscriptions; + +/** + * Extension of {@link SubscriptionEmailNotificationCli} for CLI. + */ +public class SubscriptionEmailNotificationCliScriptConfiguration + extends SubscriptionEmailNotificationConfiguration { + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java new file mode 100644 index 000000000000..0ff81bcb56c8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java @@ -0,0 +1,62 @@ +/** + * 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.subscriptions; + +import java.sql.SQLException; +import java.util.Objects; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.AuthorizeServiceImpl; +import org.dspace.core.Context; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them + */ +public class SubscriptionEmailNotificationConfiguration extends ScriptConfiguration { + + private Class dspaceRunnableClass; + + @Autowired + private AuthorizeServiceImpl authorizeService; + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (Objects.isNull(options)) { + Options options = new Options(); + options.addOption("f", "Frequency", true, "Subscription frequency. It can have value D, M or W"); + options.getOption("f").setRequired(true); + super.options = options; + } + return options; + } + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java new file mode 100644 index 000000000000..196ee84128f4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java @@ -0,0 +1,144 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.subscriptions; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.service.SubscribeService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.dspace.subscriptions.service.DSpaceObjectUpdates; +import org.dspace.subscriptions.service.SubscriptionGenerator; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.LazyInitializer; + +/** + * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them + * + * @author alba aliu + */ +public class SubscriptionEmailNotificationService { + + private static final Logger log = LogManager.getLogger(SubscriptionEmailNotification.class); + + public static final List FREQUENCIES = Arrays.asList("D", "W", "M"); + + private Map contentUpdates = new HashMap<>(); + @SuppressWarnings("rawtypes") + private Map generators = new HashMap<>(); + @SuppressWarnings("rawtypes") + private List communities = new ArrayList<>(); + @SuppressWarnings("rawtypes") + private List collections = new ArrayList<>(); + @SuppressWarnings("rawtypes") + private List items = new ArrayList<>(); + + private final SubscribeService subscribeService; + + @SuppressWarnings("unchecked") + public void perform(Context context, DSpaceRunnableHandler handler, String type, String frequency) { + try { + context.turnOffAuthorisationSystem(); + List subscriptionList = findAllSubscriptionsByTypeAndFrequency(context, type, frequency); + // if content subscription + // Here is verified if type is "content" Or "statistics" as them are configured + if (type.equals(generators.keySet().toArray()[0])) { + // the list of the person who has subscribed + int iterator = 0; + for (Subscription subscription : subscriptionList) { + DSpaceObject dSpaceObject = getdSpaceObject(subscription); + if (dSpaceObject instanceof Community) { + communities.addAll(contentUpdates.get(Community.class.getSimpleName().toLowerCase(Locale.ROOT)) + .findUpdates(context, dSpaceObject, frequency)); + } else if (dSpaceObject instanceof Collection) { + collections.addAll(contentUpdates.get(Collection.class.getSimpleName().toLowerCase(Locale.ROOT)) + .findUpdates(context, dSpaceObject, frequency)); + } else if (dSpaceObject instanceof Item) { + items.addAll(contentUpdates.get(Item.class.getSimpleName().toLowerCase(Locale.ROOT)) + .findUpdates(context, dSpaceObject, frequency)); + } + var ePerson = subscription.getePerson(); + if (iterator < subscriptionList.size() - 1) { + if (ePerson.equals(subscriptionList.get(iterator + 1).getePerson())) { + iterator++; + continue; + } else { + generators.get(type) + .notifyForSubscriptions(context, ePerson, communities, collections, items); + communities.clear(); + collections.clear(); + items.clear(); + } + } else { + //in the end of the iteration + generators.get(type).notifyForSubscriptions(context, ePerson, communities, collections, items); + } + iterator++; + } + } else { + throw new IllegalArgumentException("Currently this type:" + type + + " of subscription is not supported!"); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + handler.handleException(e); + context.abort(); + } finally { + context.restoreAuthSystemState(); + } + } + + private DSpaceObject getdSpaceObject(Subscription subscription) { + DSpaceObject dSpaceObject = subscription.getdSpaceObject(); + if (subscription.getdSpaceObject() instanceof HibernateProxy) { + HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); + LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); + dSpaceObject = (DSpaceObject) initializer.getImplementation(); + } + return dSpaceObject; + } + + private List findAllSubscriptionsByTypeAndFrequency(Context context, String type, String frequency) { + try { + return this.subscribeService.findAllSubscriptionsByTypeAndFrequency(context, type, frequency) + .stream() + .sorted(Comparator.comparing(s -> s.getePerson().getID())) + .collect(Collectors.toList()); + } catch (SQLException e) { + log.error(e.getMessage()); + } + return new ArrayList(); + } + + @SuppressWarnings("rawtypes") + public SubscriptionEmailNotificationService(SubscribeService subscribeService, + Map generators, + Map contentUpdates) { + this.subscribeService = subscribeService; + this.generators = generators; + this.contentUpdates = contentUpdates; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java new file mode 100644 index 000000000000..31aa78f52135 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java @@ -0,0 +1,48 @@ +/** + * 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.subscriptions.dSpaceObjectsUpdates; + +import java.util.List; + +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.subscriptions.service.DSpaceObjectUpdates; + +/** + * Class which will be used to find + * all collection objects updated related with subscribed DSO + * + * @author Alba Aliu + */ +public class CollectionsUpdates implements DSpaceObjectUpdates { + + private final SearchService searchService; + + @Override + @SuppressWarnings("rawtypes") + public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) + throws SearchServiceException { + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.addFilterQueries("search.resourcetype:" + Item.class.getSimpleName()); + discoverQuery.addFilterQueries("location.coll:(" + dSpaceObject.getID() + ")"); + discoverQuery.addFilterQueries("lastModified_dt:" + findLastFrequency(frequency)); + DiscoverResult discoverResult = searchService.search(context, discoverQuery); + return discoverResult.getIndexableObjects(); + } + + public CollectionsUpdates(SearchService searchService) { + this.searchService = searchService; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java new file mode 100644 index 000000000000..ada51cc5f7ab --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java @@ -0,0 +1,48 @@ +/** + * 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.subscriptions.dSpaceObjectsUpdates; + +import java.util.List; + +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.subscriptions.service.DSpaceObjectUpdates; + +/** + * Class which will be used to find + * all community objects updated related with subscribed DSO + * + * @author Alba Aliu + */ +public class CommunityUpdates implements DSpaceObjectUpdates { + + private SearchService searchService; + + @Override + @SuppressWarnings("rawtypes") + public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) + throws SearchServiceException { + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.addFilterQueries("search.resourcetype:" + Item.class.getSimpleName()); + discoverQuery.addFilterQueries("location.comm:(" + dSpaceObject.getID() + ")"); + discoverQuery.addFilterQueries("lastModified_dt:" + this.findLastFrequency(frequency)); + DiscoverResult discoverResult = searchService.search(context, discoverQuery); + return discoverResult.getIndexableObjects(); + } + + public CommunityUpdates(SearchService searchService) { + this.searchService = searchService; + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java new file mode 100644 index 000000000000..de8bd2beb7fc --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java @@ -0,0 +1,193 @@ +/** + * 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.subscriptions.dSpaceObjectsUpdates; + +import static org.apache.commons.lang3.StringUtils.trimToEmpty; + +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.configuration.DiscoveryConfiguration; +import org.dspace.discovery.configuration.DiscoveryConfigurationService; +import org.dspace.discovery.configuration.DiscoveryRelatedItemConfiguration; +import org.dspace.discovery.configuration.DiscoverySortConfiguration; +import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration; +import org.dspace.discovery.configuration.DiscoverySortFunctionConfiguration; +import org.dspace.discovery.indexobject.IndexableCollection; +import org.dspace.discovery.indexobject.IndexableCommunity; +import org.dspace.discovery.indexobject.IndexableItem; +import org.dspace.subscriptions.service.DSpaceObjectUpdates; + +/** + * Class which will be used to find + * all item objects updated related with subscribed DSO + * + * @author Alba Aliu + */ +public class ItemsUpdates implements DSpaceObjectUpdates { + + private final Logger log = LogManager.getLogger(ItemsUpdates.class); + + private final CollectionService collectionService; + private final CommunityService communityService; + private final ItemService itemService; + private DiscoveryConfigurationService searchConfigurationService; + private SearchService searchService; + + @Override + @SuppressWarnings("rawtypes") + public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) { + List list = new ArrayList<>(); + // entity type found + String inverseRelationName = "RELATION." + itemService.getEntityTypeLabel((Item) dSpaceObject); + List discoveryConfigurationList = + searchConfigurationService.getDiscoveryConfigurationWithPrefixName(inverseRelationName); + DiscoverQuery discoverQuery = null; + DiscoverResult searchResult = null; + IndexableObject indexableObject = resolveScope(context, dSpaceObject.getID().toString()); + try { + for (DiscoveryConfiguration discoveryConfiguration : discoveryConfigurationList) { + discoverQuery = buildDiscoveryQuery(discoveryConfiguration, indexableObject); + discoverQuery.addFilterQueries("lastModified_dt:" + this.findLastFrequency(frequency)); + searchResult = searchService.search(context, discoverQuery); + list.addAll(searchResult.getIndexableObjects()); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return list; + } + + private IndexableObject resolveScope(Context context, String scope) { + IndexableObject scopeObj = null; + if (StringUtils.isBlank(scope)) { + return scopeObj; + } + try { + UUID uuid = UUID.fromString(scope); + scopeObj = new IndexableCommunity(communityService.find(context, uuid)); + if (scopeObj.getIndexedObject() == null) { + scopeObj = new IndexableCollection(collectionService.find(context, uuid)); + } + if (scopeObj.getIndexedObject() == null) { + scopeObj = new IndexableItem(itemService.find(context, uuid)); + } + } catch (IllegalArgumentException e) { + log.error("The given scope string " + trimToEmpty(scope) + " is not a UUID"); + } catch (SQLException e) { + log.error("Unable to retrieve DSpace Object with ID " + trimToEmpty(scope) + " from the database"); + } + return scopeObj; + } + + private DiscoverQuery buildDiscoveryQuery(DiscoveryConfiguration discoveryConfiguration, IndexableObject scope) + throws SQLException { + DiscoverQuery discoverQuery = buildBaseQuery(discoveryConfiguration, scope); + discoverQuery.addDSpaceObjectFilter(IndexableItem.TYPE); + configureSorting(discoverQuery, discoveryConfiguration.getSearchSortConfiguration(), scope); + return discoverQuery; + } + + @SuppressWarnings("rawtypes") + private void configureSorting(DiscoverQuery queryArgs, DiscoverySortConfiguration searchSortConfiguration, + final IndexableObject scope) { + String sortBy = getDefaultSortField(searchSortConfiguration); + String sortOrder = getDefaultSortDirection(searchSortConfiguration); + //Update Discovery query + DiscoverySortFieldConfiguration sortFieldConfiguration = searchSortConfiguration + .getSortFieldConfiguration(sortBy); + + if (Objects.nonNull(sortFieldConfiguration)) { + String sortField; + if (DiscoverySortFunctionConfiguration.SORT_FUNCTION.equals(sortFieldConfiguration.getType())) { + sortField = MessageFormat.format( + ((DiscoverySortFunctionConfiguration) sortFieldConfiguration).getFunction(scope.getID()), + scope.getID()); + } else { + var type = sortFieldConfiguration.getType(); + var metadataField = sortFieldConfiguration.getMetadataField(); + sortField = searchService.toSortFieldIndex(metadataField, type); + } + + if ("asc".equalsIgnoreCase(sortOrder)) { + queryArgs.setSortField(sortField, DiscoverQuery.SORT_ORDER.asc); + } else if ("desc".equalsIgnoreCase(sortOrder)) { + queryArgs.setSortField(sortField, DiscoverQuery.SORT_ORDER.desc); + } else { + log.error(sortOrder + " is not a valid sort order"); + } + + } else { + log.error(sortBy + " is not a valid sort field"); + } + } + + private String getDefaultSortDirection(DiscoverySortConfiguration searchSortConfiguration) { + return searchSortConfiguration.getSortFields().iterator().next().getDefaultSortOrder().toString(); + } + + private String getDefaultSortField(DiscoverySortConfiguration searchSortConfiguration) { + String sortBy;// Attempt to find the default one, if none found we use SCORE + sortBy = "score"; + if (Objects.nonNull(searchSortConfiguration.getSortFields()) && + !searchSortConfiguration.getSortFields().isEmpty()) { + DiscoverySortFieldConfiguration defaultSort = searchSortConfiguration.getSortFields().get(0); + if (org.apache.commons.lang.StringUtils.isBlank(defaultSort.getMetadataField())) { + return sortBy; + } + sortBy = defaultSort.getMetadataField(); + } + return sortBy; + } + + private DiscoverQuery buildBaseQuery(DiscoveryConfiguration discoveryConfiguration, IndexableObject scope) { + DiscoverQuery discoverQuery = new DiscoverQuery(); + if (Objects.isNull(discoveryConfiguration)) { + return discoverQuery; + } + + discoverQuery.setDiscoveryConfigurationName(discoveryConfiguration.getId()); + List filterQueries = discoveryConfiguration.getDefaultFilterQueries(); + + for (String filterQuery : filterQueries) { + if (discoveryConfiguration instanceof DiscoveryRelatedItemConfiguration) { + discoverQuery.addFilterQueries(MessageFormat.format(filterQuery, scope.getID())); + } else { + discoverQuery.addFilterQueries(filterQuery); + } + } + return discoverQuery; + } + + public ItemsUpdates(CollectionService collectionService, CommunityService communityService, ItemService itemService, + DiscoveryConfigurationService searchConfigurationService, SearchService searchService) { + this.collectionService = collectionService; + this.communityService = communityService; + this.itemService = itemService; + this.searchConfigurationService = searchConfigurationService; + this.searchService = searchService; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java new file mode 100644 index 000000000000..96de54f1ecfa --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.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.subscriptions.service; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; + +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.SearchServiceException; + +/** + * Interface class which will be used to find all objects updated related with subscribed DSO + * + * @author Alba Aliu + */ +public interface DSpaceObjectUpdates { + + /** + * Send an email to some addresses, concerning a Subscription, using a given dso. + * + * @param context current DSpace session. + */ + @SuppressWarnings("rawtypes") + public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) + throws SearchServiceException; + + default String findLastFrequency(String frequency) { + String startDate = ""; + String endDate = ""; + Calendar cal = Calendar.getInstance(); + // Full ISO 8601 is e.g. + SimpleDateFormat fullIsoStart = new SimpleDateFormat("yyyy-MM-dd'T'00:00:00'Z'"); + SimpleDateFormat fullIsoEnd = new SimpleDateFormat("yyyy-MM-dd'T'23:59:59'Z'"); + switch (frequency) { + case "D": + cal.add(Calendar.DAY_OF_MONTH, -1); + endDate = fullIsoEnd.format(cal.getTime()); + startDate = fullIsoStart.format(cal.getTime()); + break; + case "M": + int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); + cal.add(Calendar.DAY_OF_MONTH, -dayOfMonth); + endDate = fullIsoEnd.format(cal.getTime()); + cal.add(Calendar.MONTH, -1); + cal.add(Calendar.DAY_OF_MONTH, 1); + startDate = fullIsoStart.format(cal.getTime()); + break; + case "W": + cal.add(Calendar.DAY_OF_WEEK, -1); + int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) - 1; + cal.add(Calendar.DAY_OF_WEEK, -dayOfWeek); + endDate = fullIsoEnd.format(cal.getTime()); + cal.add(Calendar.DAY_OF_WEEK, -6); + startDate = fullIsoStart.format(cal.getTime()); + break; + default: + return null; + } + return "[" + startDate + " TO " + endDate + "]"; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java new file mode 100644 index 000000000000..994ada75b61b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.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.subscriptions.service; + +import java.util.List; + +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * Interface Class which will be used to send email notifications to ePerson + * containing information for all list of objects. + * + * @author Alba Aliu + */ +public interface SubscriptionGenerator { + + public void notifyForSubscriptions(Context c, EPerson ePerson, List comm, List coll, List items); + +} \ No newline at end of file diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index f76cbe88c601..fa7c97eccdc2 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -80,4 +80,10 @@ + + + + + + diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml new file mode 100644 index 000000000000..97c303ec5450 --- /dev/null +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dc.title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0f38f261c2e55f01c6cd144a3d3d95ab12d169e2 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 15 Dec 2022 14:22:25 -0500 Subject: [PATCH 129/394] Rename option to parallel the exporter. --- .../app/itemimport/ItemImportCLIScriptConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java index 4e2c41f0c0aa..d265cbf4a1d6 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportCLIScriptConfiguration.java @@ -13,7 +13,7 @@ /** * The {@link ScriptConfiguration} for the {@link ItemImportCLI} script - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class ItemImportCLIScriptConfiguration extends ItemImportScriptConfiguration { @@ -55,7 +55,7 @@ public Options getOptions() { options.addOption(Option.builder("v").longOpt("validate") .desc("test run - do not actually import items") .hasArg(false).required(false).build()); - options.addOption(Option.builder("x").longOpt("exclude_content") + options.addOption(Option.builder("x").longOpt("exclude-bitstreams") .desc("do not load or expect content bitstreams") .hasArg(false).required(false).build()); options.addOption(Option.builder("p").longOpt("template") From ea6876f2fe992e995366c684dba6cc4fb838f42a Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 15 Dec 2022 14:22:59 -0500 Subject: [PATCH 130/394] Add option to GUI as well. --- .../dspace/app/itemimport/ItemImportScriptConfiguration.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java index fc761af0fac1..a3149040c49b 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportScriptConfiguration.java @@ -19,7 +19,7 @@ /** * The {@link ScriptConfiguration} for the {@link ItemImport} script - * + * * @author Francesco Pio Scognamiglio (francescopio.scognamiglio at 4science.com) */ public class ItemImportScriptConfiguration extends ScriptConfiguration { @@ -81,6 +81,9 @@ public Options getOptions() { options.addOption(Option.builder("v").longOpt("validate") .desc("test run - do not actually import items") .hasArg(false).required(false).build()); + options.addOption(Option.builder("x").longOpt("exclude-bitstreams") + .desc("do not load or expect content bitstreams") + .hasArg(false).required(false).build()); options.addOption(Option.builder("p").longOpt("template") .desc("apply template") .hasArg(false).required(false).build()); From bc6066523f7be9d00aaa117bc016f7ecba6257dd Mon Sep 17 00:00:00 2001 From: Adan Roman Date: Fri, 16 Dec 2022 09:38:48 +0100 Subject: [PATCH 131/394] #8412 In Submission Form, list collections alphabetically - solr ordering --- .../authorize/AuthorizeServiceImpl.java | 19 ++++++++++++------- .../dspace/content/CollectionServiceImpl.java | 3 +++ .../content/service/DSpaceObjectService.java | 6 +++++- .../repository/CollectionRestRepository.java | 7 ------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index a9874afda621..c427b072409c 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -31,14 +31,17 @@ import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverQuery.SORT_ORDER; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.configuration.DiscoveryConfigurationParameters.SORT; import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.discovery.indexobject.IndexableCommunity; import org.dspace.eperson.EPerson; @@ -830,7 +833,7 @@ public List findAdminAuthorizedCommunity(Context context, String quer query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCommunity.TYPE, - offset, limit); + offset, limit, null, null); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Community community = ((IndexableCommunity) solrCollections).getIndexedObject(); communities.add(community); @@ -852,7 +855,7 @@ public long countAdminAuthorizedCommunity(Context context, String query) query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCommunity.TYPE, - null, null); + null, null, null, null); return discoverResult.getTotalSearchResults(); } @@ -877,7 +880,7 @@ public List findAdminAuthorizedCollection(Context context, String qu query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCollection.TYPE, - offset, limit); + offset, limit,DSpaceObjectService.COMMUNITIES_AND_COLLECTIONS_SORT_FIELD,SORT_ORDER.asc); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Collection collection = ((IndexableCollection) solrCollections).getIndexedObject(); collections.add(collection); @@ -899,7 +902,7 @@ public long countAdminAuthorizedCollection(Context context, String query) query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCollection.TYPE, - null, null); + null, null, null, null); return discoverResult.getTotalSearchResults(); } @@ -919,7 +922,7 @@ private boolean performCheck(Context context, String query) throws SQLException } try { - DiscoverResult discoverResult = getDiscoverResult(context, query, null, null); + DiscoverResult discoverResult = getDiscoverResult(context, query, null, null, null, null); if (discoverResult.getTotalSearchResults() > 0) { return true; } @@ -931,7 +934,7 @@ private boolean performCheck(Context context, String query) throws SQLException return false; } - private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit) + private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit, String sortField, SORT_ORDER sortOrder) throws SearchServiceException, SQLException { String groupQuery = getGroupToQuery(groupService.allMemberGroups(context, context.getCurrentUser())); @@ -947,7 +950,9 @@ private DiscoverResult getDiscoverResult(Context context, String query, Integer if (limit != null) { discoverQuery.setMaxResults(limit); } - + if (sortField != null && sortOrder != null) { + discoverQuery.setSortField(sortField, sortOrder); + } return searchService.search(context, discoverQuery); } diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index e54f609389d8..00e601248a20 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -43,6 +43,7 @@ import org.dspace.core.LogHelper; import org.dspace.core.service.LicenseService; import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverQuery.SORT_ORDER; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; @@ -946,6 +947,7 @@ public List findCollectionsWithSubmit(String q, Context context, Com discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); + discoverQuery.setSortField(COMMUNITIES_AND_COLLECTIONS_SORT_FIELD, SORT_ORDER.asc); DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, null, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); @@ -1025,6 +1027,7 @@ public List findCollectionsWithSubmit(String q, Context context, Com discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); + discoverQuery.setSortField(COMMUNITIES_AND_COLLECTIONS_SORT_FIELD, SORT_ORDER.asc); DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, entityType, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { diff --git a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java index 606f5bb7c02f..47ed6bb66a24 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java @@ -52,7 +52,11 @@ public interface DSpaceObjectService { = new MetadataFieldName(DC, "format"); public static final MetadataFieldName MD_SOURCE = new MetadataFieldName(DC, "source"); - + /* + * Field used to sort community and collection lists at solr + */ + public static final String COMMUNITIES_AND_COLLECTIONS_SORT_FIELD = "dc.title_sort"; + /** * Generic find for when the precise type of an Entity is not known * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index 8f5150ae9296..ba3163a4447c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -9,7 +9,6 @@ import java.io.IOException; import java.sql.SQLException; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -46,7 +45,6 @@ import org.dspace.content.Community; import org.dspace.content.EntityType; import org.dspace.content.Item; -import org.dspace.content.comparator.NameAscendingComparator; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -187,7 +185,6 @@ public Page findSubmitAuthorizedByCommunity( List collections = cs.findCollectionsWithSubmit(q, context, com, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - Collections.sort(collections, new NameAscendingComparator()); int tot = cs.countCollectionsWithSubmit(q, context, com); return converter.toRestPage(collections, pageable, tot , utils.obtainProjection()); } catch (SQLException | SearchServiceException e) { @@ -203,7 +200,6 @@ public Page findSubmitAuthorized(@Parameter(value = "query") Str List collections = cs.findCollectionsWithSubmit(q, context, null, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - Collections.sort(collections, new NameAscendingComparator()); int tot = cs.countCollectionsWithSubmit(q, context, null); return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); } catch (SQLException e) { @@ -220,7 +216,6 @@ public Page findAdminAuthorized ( List collections = authorizeService.findAdminAuthorizedCollection(context, query, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - Collections.sort(collections, new NameAscendingComparator()); long tot = authorizeService.countAdminAuthorizedCollection(context, query); return converter.toRestPage(collections, pageable, tot , utils.obtainProjection()); } catch (SearchServiceException | SQLException e) { @@ -253,7 +248,6 @@ public Page findSubmitAuthorizedByEntityType( List collections = cs.findCollectionsWithSubmit(query, context, null, entityTypeLabel, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - Collections.sort(collections, new NameAscendingComparator()); int tot = cs.countCollectionsWithSubmit(query, context, null, entityTypeLabel); return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); } catch (SQLException e) { @@ -291,7 +285,6 @@ public Page findSubmitAuthorizedByCommunityAndEntityType( List collections = cs.findCollectionsWithSubmit(query, context, community, entityTypeLabel, Math.toIntExact(pageable.getOffset()), Math.toIntExact(pageable.getPageSize())); - Collections.sort(collections, new NameAscendingComparator()); int total = cs.countCollectionsWithSubmit(query, context, community, entityTypeLabel); return converter.toRestPage(collections, pageable, total, utils.obtainProjection()); } catch (SQLException | SearchServiceException e) { From 193f0a4750ab3ade83d662fd40fb07c845e4f396 Mon Sep 17 00:00:00 2001 From: Adan Roman Date: Fri, 16 Dec 2022 10:14:01 +0100 Subject: [PATCH 132/394] checkstyle --- .../main/java/org/dspace/authorize/AuthorizeServiceImpl.java | 4 ++-- .../java/org/dspace/content/service/DSpaceObjectService.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index f6444ea95b8e..cb02dfaf0609 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -41,7 +41,6 @@ import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; -import org.dspace.discovery.configuration.DiscoveryConfigurationParameters.SORT; import org.dspace.discovery.indexobject.IndexableCollection; import org.dspace.discovery.indexobject.IndexableCommunity; import org.dspace.eperson.EPerson; @@ -934,7 +933,8 @@ private boolean performCheck(Context context, String query) throws SQLException return false; } - private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit, String sortField, SORT_ORDER sortOrder) + private DiscoverResult getDiscoverResult(Context context, String query, Integer offset, Integer limit, + String sortField, SORT_ORDER sortOrder) throws SearchServiceException, SQLException { String groupQuery = getGroupToQuery(groupService.allMemberGroups(context, context.getCurrentUser())); diff --git a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java index 47ed6bb66a24..31a33885ba73 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java @@ -56,7 +56,7 @@ public interface DSpaceObjectService { * Field used to sort community and collection lists at solr */ public static final String COMMUNITIES_AND_COLLECTIONS_SORT_FIELD = "dc.title_sort"; - + /** * Generic find for when the precise type of an Entity is not known * From 6195127e813fd49c31723699213caa0163bffd04 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Fri, 16 Dec 2022 12:49:30 +0100 Subject: [PATCH 133/394] 97080: Facet search on all words: authorityKey fix + ITs --- .../org/dspace/discovery/SolrServiceImpl.java | 4 +- .../indexobject/ItemIndexFactoryImpl.java | 21 +- .../app/rest/DiscoveryRestControllerIT.java | 331 +++++++++++++++++- .../app/rest/matcher/FacetValueMatcher.java | 11 +- 4 files changed, 352 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index 3ca9e38fec72..c08c233b8683 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -1253,7 +1253,7 @@ public String toSortFieldIndex(String metadataField, String type) { } /** - * Gets the solr field that contains the the facet value split on each word break to the end, so can be searched + * Gets the solr field that contains the facet value split on each word break to the end, so can be searched * on each word in the value, see {@link org.dspace.discovery.indexobject.ItemIndexFactoryImpl * #saveFacetPrefixParts(SolrInputDocument, DiscoverySearchFilter, String, String)} * Ony applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular @@ -1354,7 +1354,7 @@ protected String transformAuthorityValue(Context context, String field, String v return value; } if (field.endsWith("_filter") || field.endsWith("_ac") - || field.endsWith("_acid")) { + || field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) { //We have a filter make sure we split ! String separator = DSpaceServicesFactory.getInstance().getConfigurationService() .getProperty("discovery.solr.facets.split.char"); diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index e90de0ddc0bc..99b3827fb3b4 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -472,7 +472,7 @@ public void addDiscoveryFields(SolrInputDocument doc, Context context, Item item + var); } } - // if searchFilter is of type "facet", delegate to indexFacetFilters method + // if searchFilter is of type "facet", delegate to indexIfFilterTypeFacet method if (searchFilter.getFilterType().equals(DiscoverySearchFilterFacet.FILTER_TYPE_FACET)) { indexIfFilterTypeFacet(doc, searchFilter, value, date, authority, preferedLabel, separator); @@ -695,7 +695,7 @@ private void indexIfFilterTypeFacet(SolrInputDocument doc, DiscoverySearchFilter value.toLowerCase() + separator + value); } //Also add prefix field with all parts of value - saveFacetPrefixParts(doc, searchFilter, value, separator); + saveFacetPrefixParts(doc, searchFilter, value, separator, authority, preferedLabel); } else if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) { if (date != null) { String indexField = searchFilter.getIndexFieldName() + ".year"; @@ -762,7 +762,7 @@ private void indexIfFilterTypeFacet(SolrInputDocument doc, DiscoverySearchFilter } } //Also add prefix field with all parts of value - saveFacetPrefixParts(doc, searchFilter, value, separator); + saveFacetPrefixParts(doc, searchFilter, value, separator, authority, preferedLabel); } } @@ -783,16 +783,23 @@ private void indexIfFilterTypeFacet(SolrInputDocument doc, DiscoverySearchFilter * @param value the metadata value * @param separator the separator being used to separate value part and original value */ - private void saveFacetPrefixParts(SolrInputDocument doc, DiscoverySearchFilter searchFilter, - String value, String separator) { + private void saveFacetPrefixParts(SolrInputDocument doc, DiscoverySearchFilter searchFilter, String value, + String separator, String authority, String preferedLabel) { value = StringUtils.normalizeSpace(value); Pattern pattern = Pattern.compile("\\b\\w+\\b", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(value); while (matcher.find()) { int index = matcher.start(); String currentPart = StringUtils.substring(value, index); - doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, - currentPart.toLowerCase() + separator + value); + if (authority != null) { + String facetValue = preferedLabel != null ? preferedLabel : currentPart; + doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, + facetValue.toLowerCase() + separator + value + + SearchUtils.AUTHORITY_SEPARATOR + authority); + } else { + doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, + currentPart.toLowerCase() + separator + value); + } } } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index 440b196e1bc9..63e5e8e7f28b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -423,8 +423,9 @@ public void discoverFacetsAuthorTestWithPrefix_Capital_And_Special_Chars() throw getClient().perform(get("/api/discover/facets/author") .param("prefix", "Smith")) .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("Smith, John")))); + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entryFacetWithoutSelfLink("Smith, John"), + FacetValueMatcher.entryFacetWithoutSelfLink("stIjn, SmITH")))); getClient().perform(get("/api/discover/facets/author") .param("prefix", "S")) @@ -433,6 +434,9 @@ public void discoverFacetsAuthorTestWithPrefix_Capital_And_Special_Chars() throw .entryFacetWithoutSelfLink("Smith, John"), FacetValueMatcher .entryFacetWithoutSelfLink("S’Idan, Mo"), + // gets returned once for smith, once for stijn + FacetValueMatcher + .entryFacetWithoutSelfLink("stIjn, SmITH"), FacetValueMatcher .entryFacetWithoutSelfLink("stIjn, SmITH")))); @@ -489,12 +493,99 @@ public void discoverFacetsAuthorTestWithPrefix_Capital_And_Special_Chars() throw containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("I.T.")))); getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "?U")) + .param("prefix", "U")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.values", containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("?Unknown")))); } + @Test + public void discoverFacetsAuthorTestWithPrefixFirstName() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Parent Collection").build(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Item 1") + .withAuthor("Smith, John") + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Item 2") + .withAuthor("Smith, Jane") + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "john")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.entryAuthor("Smith, John")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "jane")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.entryAuthor("Smith, Jane")))); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "j")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.entryAuthor("Smith, John"), + FacetValueMatcher.entryAuthor("Smith, Jane")))); + } + + @Test + public void discoverFacetsAuthorWithAuthorityTestWithPrefixFirstName() throws Exception { + configurationService.setProperty("choices.plugin.dc.contributor.author", "SolrAuthorAuthority"); + configurationService.setProperty("authority.controlled.dc.contributor.author", "true"); + + metadataAuthorityService.clearCache(); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Parent Collection").build(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Item 1") + .withAuthor("Smith, John", "test_authority_1", Choices.CF_ACCEPTED) + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Item 2") + .withAuthor("Smith, Jane", "test_authority_2", Choices.CF_ACCEPTED) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/author") + .param("prefix", "j")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", + containsInAnyOrder( + FacetValueMatcher.entryAuthorWithAuthority( + "Smith, John", "test_authority_1", 1), + FacetValueMatcher.entryAuthorWithAuthority( + "Smith, Jane", "test_authority_2", 1)))); + + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + + metadataAuthorityService.clearCache(); + } + @Test public void discoverFacetsAuthorTestForHasMoreFalse() throws Exception { //Turn of the authorization system so that we can create the structure specified below @@ -5982,4 +6073,238 @@ public void discoverFacetsTestWithDsoTypeTest() throws Exception { .andExpect(jsonPath("$._embedded.values").value(Matchers.hasSize(1))); } + + @Test + public void discoverFacetsSubjectTestWithCapitalAndSpecialChars() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Parent Collection").build(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Item 1") + .withSubject("Value with: Multiple Words ") + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Item 2") + .withSubject("Multiple worded subject ") + .build(); + + Item item3 = ItemBuilder.createItem(context, collection) + .withTitle("Item 3") + .withSubject("Subject with a lot of Word values") + .build(); + + Item item4 = ItemBuilder.createItem(context, collection) + .withTitle("Item 4") + .withSubject("With, Values") + .build(); + + Item item5 = ItemBuilder.createItem(context, collection) + .withTitle("Item 5") + .withSubject("Test:of:the:colon") + .build(); + + Item item6 = ItemBuilder.createItem(context, collection) + .withTitle("Item 6") + .withSubject("Test,of,comma") + .build(); + + Item item7 = ItemBuilder.createItem(context, collection) + .withTitle("Item 7") + .withSubject("N’guyen") + .build(); + + Item item8 = ItemBuilder.createItem(context, collection) + .withTitle("Item 8") + .withSubject("test;Semicolon") + .build(); + + Item item9 = ItemBuilder.createItem(context, collection) + .withTitle("Item 9") + .withSubject("test||of|Pipe") + .build(); + + Item item10 = ItemBuilder.createItem(context, collection) + .withTitle("Item 10") + .withSubject("Test-Subject") + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "with a lot of word")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "multiple words")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Value with: Multiple Words", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "mUltiPle wor")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Multiple worded subject", 1), + FacetValueMatcher.entrySubject("Value with: Multiple Words", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "with")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("With, Values", 1), + FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1), + FacetValueMatcher.entrySubject("Value with: Multiple Words", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "of")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1), + FacetValueMatcher.entrySubject("Test,of,comma", 1), + FacetValueMatcher.entrySubject("Test:of:the:colon", 1), + FacetValueMatcher.entrySubject("test||of|Pipe", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "tEsT")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Test,of,comma", 1), + FacetValueMatcher.entrySubject("Test-Subject", 1), + FacetValueMatcher.entrySubject("Test:of:the:colon", 1), + FacetValueMatcher.entrySubject("test;Semicolon", 1), + FacetValueMatcher.entrySubject("test||of|Pipe", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "colon")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Test:of:the:colon", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "coMma")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Test,of,comma", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "guyen")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("N’guyen", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "semiColon")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("test;Semicolon", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "pipe")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("test||of|Pipe", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Subject")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubject("Multiple worded subject", 1), + FacetValueMatcher.entrySubject("Test-Subject", 1), + FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Subject of word")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values").isEmpty()) + .andExpect(jsonPath("$.page.number", is(0))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Value with words")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values").isEmpty()) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void discoverFacetsSubjectWithAuthorityTest() throws Exception { + configurationService.setProperty("choices.plugin.dc.subject", "SolrSubjectAuthority"); + configurationService.setProperty("authority.controlled.dc.subject", "true"); + + metadataAuthorityService.clearCache(); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Parent Collection").build(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Item 1") + .withSubject("Value with: Multiple Words", + "test_authority_1", Choices.CF_ACCEPTED) + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Item 2") + .withSubject("Multiple worded subject ", + "test_authority_2", Choices.CF_ACCEPTED) + .build(); + + Item item3 = ItemBuilder.createItem(context, collection) + .withTitle("Item 3") + .withSubject("Subject with a lot of Word values", + "test_authority_3", Choices.CF_ACCEPTED) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "with a lot of word")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubjectWithAuthority("Subject with a lot of Word values", + "test_authority_3", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "mUltiPle wor")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubjectWithAuthority("Multiple worded subject", + "test_authority_2", 1), + FacetValueMatcher.entrySubjectWithAuthority("Value with: Multiple Words", + "test_authority_1", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Subject")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entrySubjectWithAuthority("Multiple worded subject", + "test_authority_2", 1), + FacetValueMatcher.entrySubjectWithAuthority("Subject with a lot of Word values", + "test_authority_3", 1)))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Subject of word")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values").isEmpty()) + .andExpect(jsonPath("$.page.number", is(0))); + + getClient().perform(get("/api/discover/facets/subject") + .param("prefix", "Value with words")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.values").isEmpty()) + .andExpect(jsonPath("$.page.number", is(0))); + + metadataAuthorityService.clearCache(); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java index 2c7cebdbe973..beae0405581a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java @@ -55,15 +55,20 @@ public static Matcher entrySubject(String label, int count) { hasJsonPath("$.type", is("discover")), hasJsonPath("$.count", is(count)), hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")), - hasJsonPath("$._links.search.href", containsString("f.subject=" + label + ",equals")) + hasJsonPath("$._links.search.href", containsString( + "f.subject=" + urlPathSegmentEscaper().escape(label) + ",equals")) ); } - public static Matcher entrySubject(String label, String authority, int count) { + public static Matcher entrySubjectWithAuthority(String label, String authority, int count) { return allOf( hasJsonPath("$.authorityKey", is(authority)), - entrySubject(label, count) + hasJsonPath("$.count", is(count)), + hasJsonPath("$.label", is(label)), + hasJsonPath("$.type", is("discover")), + hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")), + hasJsonPath("$._links.search.href", containsString("f.subject=" + authority + ",authority")) ); } From 4ff2e2826de208a3071dd7b13f88bb9692aa7d40 Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Fri, 16 Dec 2022 15:54:31 +0100 Subject: [PATCH 134/394] 97080: Fix DiscoveryRestControllerIT --- .../java/org/dspace/app/rest/DiscoveryRestControllerIT.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index 63e5e8e7f28b..b5f22cc7661f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -6305,6 +6305,8 @@ public void discoverFacetsSubjectWithAuthorityTest() throws Exception { .andExpect(jsonPath("$._embedded.values").isEmpty()) .andExpect(jsonPath("$.page.number", is(0))); + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + metadataAuthorityService.clearCache(); } } From 32bafed54886ea6462ed72e32586eb641845a3be Mon Sep 17 00:00:00 2001 From: Nona Luypaert Date: Fri, 16 Dec 2022 16:35:27 +0100 Subject: [PATCH 135/394] 97080: Browse values also get indexed with _prefix field --- .../discovery/SolrServiceMetadataBrowseIndexingPlugin.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java index d03ea359f598..746a0cb83214 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceMetadataBrowseIndexingPlugin.java @@ -7,6 +7,8 @@ */ package org.dspace.discovery; +import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES; + import java.util.HashSet; import java.util.List; import java.util.Set; @@ -261,9 +263,9 @@ public void additionalIndex(Context context, IndexableObject indexableObject, So } } } - for (String facet : distFValues) { document.addField(bi.getDistinctTableName() + "_filter", facet); + document.addField(bi.getDistinctTableName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, facet); } for (String facet : distFAuths) { document.addField(bi.getDistinctTableName() From f523420a9bbb6c6fdad7196daa95a5664188819f Mon Sep 17 00:00:00 2001 From: qzhao Date: Mon, 19 Dec 2022 12:59:45 +0100 Subject: [PATCH 136/394] add test method for bugfix "collectin admin can create new version" pull request: dspace/dspace/#8522 --- .../app/rest/VersionRestRepositoryIT.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java index 5878c161430c..25cfb7532db9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java @@ -850,6 +850,57 @@ public void createVersionWithVersioningDisabledTest() throws Exception { .andExpect(status().isUnauthorized()); } + @Test + public void createNewVersionItemByCollectionAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community rootCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + EPerson colAdmin = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("coladmin@email.com") + .withPassword(password) + .withNameInMetadata("Collection", "Admin") + .build(); + Collection col = CollectionBuilder + .createCollection(context, rootCommunity) + .withName("Collection 1") + .withAdminGroup(colAdmin) + .build(); + + Item item = ItemBuilder.createItem(context, col) + .withTitle("Public test item") + .withIssueDate("2022-12-19") + .withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + item.setSubmitter(eperson); + System.out.println(item.getSubmitter().getEmail().toString()); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + String token = getAuthToken(colAdmin.getEmail(), password); + try { + getClient(token).perform(post("/api/versioning/versions") + .param("summary", "test summary!") + .contentType(MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/core/items/" + item.getID())) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.version", is(2)), + hasJsonPath("$.summary", is("test summary!")), + hasJsonPath("$.type", is("version")) + ))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + System.out.println("version 2: " + item.getSubmitter().getEmail().toString()); + } finally { + VersionBuilder.delete(idRef.get()); + } + } + @Test public void patchReplaceSummaryTest() throws Exception { context.turnOffAuthorisationSystem(); From 9f456c21d54785221242033ca030219484b39ce9 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 19 Dec 2022 13:51:06 +0100 Subject: [PATCH 137/394] [CST-7756] porting of SubscribeFeature --- .../impl/CanSubscribeFeature.java | 64 ++++ .../authorization/CanSubscribeFeatureIT.java | 351 ++++++++++++++++++ 2 files changed, 415 insertions(+) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java new file mode 100644 index 000000000000..57b029c75eb7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java @@ -0,0 +1,64 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization.impl; + +import static org.dspace.core.Constants.READ; + +import java.sql.SQLException; +import java.util.Objects; + +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; +import org.dspace.app.rest.model.BaseObjectRest; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Checks if the given user can subscribe to a dataspace object + * + * @author Alba Aliu (alba.aliu at atis.al) + */ +@Component +@AuthorizationFeatureDocumentation(name = CanSubscribeFeature.NAME, + description = "Used to verify if the given user can subscribe to a dataspace object") +public class CanSubscribeFeature implements AuthorizationFeature { + + public static final String NAME = "canSubscribeDso"; + + @Autowired + private Utils utils; + @Autowired + private AuthorizeService authorizeService; + + @Override + @SuppressWarnings("rawtypes") + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + if (Objects.isNull(context.getCurrentUser())) { + return false; + } + DSpaceObject dSpaceObject = (DSpaceObject) utils.getDSpaceAPIObjectFromRest(context, object); + return authorizeService.authorizeActionBoolean(context, context.getCurrentUser(), dSpaceObject, READ, true); + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ + CommunityRest.CATEGORY + "." + CommunityRest.NAME, + CollectionRest.CATEGORY + "." + CollectionRest.NAME, + ItemRest.CATEGORY + "." + ItemRest.NAME + }; + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java new file mode 100644 index 000000000000..fe43c0a73d8c --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java @@ -0,0 +1,351 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.authorization.impl.CanSubscribeFeature; +import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.CommunityConverter; +import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.service.ResourcePolicyService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test of Subscribe Dso Feature implementation. + * + * @author Alba Aliu (alba.aliu at 4science.it) + */ +public class CanSubscribeFeatureIT extends AbstractControllerIntegrationTest { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CanSubscribeFeatureIT.class); + + @Autowired + private ItemConverter itemConverter; + @Autowired + private CollectionConverter collectionConverter; + @Autowired + private Utils utils; + @Autowired + private CommunityConverter communityConverter; + @Autowired + private AuthorizationFeatureService authorizationFeatureService; + @Autowired + private ResourcePolicyService resourcePolicyService; + + private Community communityAuthorized; + private Collection collectionAuthorized; + private AuthorizationFeature canSubscribeFeature; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); + communityAuthorized = CommunityBuilder.createCommunity(context) + .withName("communityA") + .withAdminGroup(admin) + .build(); + collectionAuthorized = CollectionBuilder.createCollection(context, communityAuthorized) + .withName("Collection A") + .withAdminGroup(admin) + .build(); + context.restoreAuthSystemState(); + canSubscribeFeature = authorizationFeatureService.find(CanSubscribeFeature.NAME); + } + + @Test + public void testCanSubscribeCommunity() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson ePersonAuthorized = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + String token = getAuthToken(ePersonAuthorized.getEmail(), password); + CommunityRest comRest = communityConverter.convert(parentCommunity, DefaultProjection.DEFAULT); + String comUri = utils.linkToSingleResource(comRest, "self").getHref(); + context.restoreAuthSystemState(); + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", comUri) + .param("feature", canSubscribeFeature.getName()) + .param("embed", "feature")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").exists()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + } + + @Test + public void anonymousCanSubscribeCommunityTest() throws Exception { + context.turnOffAuthorisationSystem(); + CommunityRest comRest = communityConverter.convert(parentCommunity, DefaultProjection.DEFAULT); + String comUri = utils.linkToSingleResource(comRest, "self").getHref(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/authz/authorizations/search/object") + .param("uri", comUri) + .param("feature", canSubscribeFeature.getName()) + .param("embed", "feature")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void anonymousCanSubscribeCollectionTest() throws Exception { + context.turnOffAuthorisationSystem(); + Collection collectionWithReadPermission = CollectionBuilder.createCollection(context, communityAuthorized) + .build(); + context.restoreAuthSystemState(); + CollectionRest collectionRest = collectionConverter.convert(collectionWithReadPermission, Projection.DEFAULT); + String comUri = utils.linkToSingleResource(collectionRest, "self").getHref(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/authz/authorizations/search/object") + .param("uri", comUri) + .param("feature", canSubscribeFeature.getName()) + .param("embed", "feature")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void anonymousCanSubscribeItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collectionAuthorized) + .withTitle("Test item") + .build(); + context.restoreAuthSystemState(); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); + String comUri = utils.linkToSingleResource(itemRest, "self").getHref(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/authz/authorizations/search/object") + .param("uri", comUri) + .param("feature", canSubscribeFeature.getName()) + .param("embed", "feature")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").doesNotExist()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + } + + @Test + public void testCanNotSubscribeItem() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + // the user to be tested is not part of the group with read permission + Group groupWithReadPermission = GroupBuilder.createGroup(context) + .withName("Group A") + .addMember(admin) + .build(); + Item item = ItemBuilder.createItem(context, collectionAuthorized) + .withTitle("Test item") + .build(); + cleanUpPermissions(resourcePolicyService.find(context, item)); + setPermissions(item, groupWithReadPermission, Constants.READ); + item.setSubmitter(eperson); + context.restoreAuthSystemState(); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); + String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); + String comUri = utils.linkToSingleResource(itemRest, "self").getHref(); + context.restoreAuthSystemState(); + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", comUri) + .param("feature", canSubscribeFeature.getName()) + .param("embed", "feature")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page").exists()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(0))) + .andExpect(jsonPath("$.page.number", is(0))) + // default value of page size + .andExpect(jsonPath("$.page.size", is(20))); + } + + @Test + public void testCanNotSubscribeCollection() throws Exception { + context.turnOffAuthorisationSystem(); + Collection collectionWithReadPermission = CollectionBuilder.createCollection(context, communityAuthorized) + .withAdminGroup(admin).build(); + EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + // the user to be tested is not part of the group with read permission + Group groupWithReadPermission = GroupBuilder.createGroup(context) + .withName("Group A") + .addMember(eperson) + .build(); + cleanUpPermissions(resourcePolicyService.find(context, collectionWithReadPermission)); + setPermissions(collectionWithReadPermission, groupWithReadPermission, Constants.READ); + context.restoreAuthSystemState(); + CollectionRest collectionRest = collectionConverter.convert(collectionWithReadPermission, Projection.DEFAULT); + String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); + String comUri = utils.linkToSingleResource(collectionRest, "self").getHref(); + context.restoreAuthSystemState(); + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", comUri) + .param("feature", canSubscribeFeature.getName()) + .param("embed", "feature")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page").exists()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(0))) + .andExpect(jsonPath("$.page.number", is(0))) + // default value of page size + .andExpect(jsonPath("$.page.size", is(20))); + } + + @Test + public void testCanNotSubscribeCommunity() throws Exception { + context.turnOffAuthorisationSystem(); + Community communityWithReadPermissions = CommunityBuilder.createCommunity(context).build(); + EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + // the user to be tested is not part of the group with read permission + Group groupWithReadPermission = GroupBuilder.createGroup(context) + .withName("Group A") + .addMember(admin) + .addMember(eperson) + .build(); + cleanUpPermissions(resourcePolicyService.find(context, communityWithReadPermissions)); + setPermissions(communityWithReadPermissions, groupWithReadPermission, Constants.READ); + context.restoreAuthSystemState(); + CommunityRest communityRest = communityConverter.convert(communityWithReadPermissions, Projection.DEFAULT); + String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); + String comUri = utils.linkToSingleResource(communityRest, "self").getHref(); + context.restoreAuthSystemState(); + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", comUri) + .param("feature", canSubscribeFeature.getName()) + .param("embed", "feature")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page").exists()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$.page.totalPages", is(0))) + .andExpect(jsonPath("$.page.number", is(0))) + // default value of page size + .andExpect(jsonPath("$.page.size", is(20))); + } + + @Test + public void testCanSubscribeCollection() throws Exception { + context.turnOffAuthorisationSystem(); + Collection collectionWithReadPermission = CollectionBuilder.createCollection(context, communityAuthorized) + .withAdminGroup(admin).build(); + EPerson ePersonSubscribePermission = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + context.restoreAuthSystemState(); + CollectionRest collectionRest = collectionConverter.convert(collectionWithReadPermission, Projection.DEFAULT); + String token = getAuthToken(ePersonSubscribePermission.getEmail(), password); + String comUri = utils.linkToSingleResource(collectionRest, "self").getHref(); + context.restoreAuthSystemState(); + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", comUri) + .param("feature", canSubscribeFeature.getName()) + .param("embed", "feature")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").exists()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + } + + @Test + public void testCanSubscribeItem() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson ePersonSubscribePermission = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + Item item = ItemBuilder.createItem(context, collectionAuthorized) + .withTitle("Test item") + .build(); + context.restoreAuthSystemState(); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); + String token = getAuthToken(ePersonSubscribePermission.getEmail(), password); + String comUri = utils.linkToSingleResource(itemRest, "self").getHref(); + context.restoreAuthSystemState(); + getClient(token).perform(get("/api/authz/authorizations/search/object") + .param("uri", comUri) + .param("feature", canSubscribeFeature.getName()) + .param("embed", "feature")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded").exists()) + .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + } + + private void setPermissions(DSpaceObject dSpaceObject, Group group, Integer permissions) { + try { + ResourcePolicyBuilder.createResourcePolicy(context) + .withDspaceObject(dSpaceObject) + .withAction(permissions) + .withGroup(group) + .build(); + } catch (SQLException | AuthorizeException sqlException) { + log.error(sqlException.getMessage()); + } + } + + private void cleanUpPermissions(List resourcePolicies) { + try { + for (ResourcePolicy resourcePolicy : resourcePolicies) { + ResourcePolicyBuilder.delete(resourcePolicy.getID()); + } + + } catch (SQLException | SearchServiceException | IOException sqlException) { + log.error(sqlException.getMessage()); + } + } + +} \ No newline at end of file From d0a91347c6a89fd222fb4d6ebcb4723816f2744e Mon Sep 17 00:00:00 2001 From: jensroets Date: Mon, 19 Dec 2022 16:53:49 +0100 Subject: [PATCH 138/394] 97414 Advanced workflow actions --- .../xmlworkflow/state/actions/Action.java | 16 +++ .../state/actions/ActionAdvancedInfo.java | 15 +++ .../state/actions/WorkflowActionConfig.java | 25 +++++ .../processingaction/ProcessingAction.java | 8 ++ .../processingaction/RatingReviewAction.java | 101 ++++++++++++++++++ .../ScoreReviewActionAdvancedInfo.java | 31 ++++++ .../config/spring/api/workflow-actions.xml | 10 ++ ...coreReviewActionAdvancedInfoConverter.java | 30 ++++++ .../converter/WorkflowActionConverter.java | 2 + .../ScoreReviewActionAdvancedInfoRest.java | 53 +++++++++ .../app/rest/model/WorkflowActionRest.java | 49 +++++++++ .../rest/WorkflowActionRestRepositoryIT.java | 48 +++++++++ .../rest/matcher/WorkflowActionMatcher.java | 2 + dspace/config/spring/api/workflow-actions.xml | 10 ++ 14 files changed, 400 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java create mode 100644 dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java create mode 100644 dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java index 0aabfab0573a..f5e58f2d3d84 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java @@ -10,7 +10,9 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; @@ -37,6 +39,8 @@ public abstract class Action { private WorkflowActionConfig parent; private static final String ERROR_FIELDS_ATTRIBUTE = "dspace.workflow.error_fields"; + private List advancedOptions = new ArrayList<>(); + private Map advancedInfo = new HashMap<>(); /** * Called when a workflow item becomes eligible for this Action. @@ -192,4 +196,16 @@ protected void addErrorField(HttpServletRequest request, String fieldName) { //save updated list setErrorFields(request, errorFields); } + + protected List getAdvancedOptions() { + return advancedOptions; + } + + protected boolean isAdvanced() { + return !advancedOptions.isEmpty(); + } + + protected Map getAdvancedInfo() { + return advancedInfo; + } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java new file mode 100644 index 000000000000..b54d4ebeca1b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java @@ -0,0 +1,15 @@ +/** + * 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.xmlworkflow.state.actions; + +public interface ActionAdvancedInfo { + boolean isDescriptionRequired(); + void setDescriptionRequired(boolean descriptionRequired); + int getMaxValue(); + void setMaxValue(int maxValue); +} diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java index 1dc61888b140..924670ee4a2b 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java @@ -8,6 +8,7 @@ package org.dspace.xmlworkflow.state.actions; import java.util.List; +import java.util.Map; import org.dspace.xmlworkflow.state.Step; @@ -69,4 +70,28 @@ public List getOptions() { return this.processingAction.getOptions(); } + /** + * Returns a list of advanced options this user has on this action, resulting in the next step of the workflow + * @returnA list of advanced options of this action, resulting in the next step of the workflow + */ + public List getAdvancedOptions() { + return this.processingAction.getAdvancedOptions(); + } + + /** + * Returns a boolean depending on whether this action has advanced options + * @return The boolean indicating whether this action has advanced options + */ + public boolean isAdvanced() { + return this.processingAction.isAdvanced(); + } + + /** + * Returns a Map of info for the advanced options this user has on this action + * @return a Map of info for the advanced options this user has on this action + */ + public Map getAdvancedInfo() { + return this.processingAction.getAdvancedInfo(); + } + } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java index 8b8358a8d632..585f3b40a28f 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java @@ -8,6 +8,7 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.sql.SQLException; +import java.util.List; import javax.servlet.http.HttpServletRequest; import org.dspace.content.service.ItemService; @@ -48,4 +49,11 @@ public boolean isAuthorized(Context context, HttpServletRequest request, XmlWork task.getStepID().equals(getParent().getStep().getId()) && task.getActionID().equals(getParent().getId()); } + + @Override + protected List getAdvancedOptions() { + List advancedOptions = super.getAdvancedOptions(); + advancedOptions.add(SUBMIT_EDIT_METADATA); + return advancedOptions; + } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java new file mode 100644 index 000000000000..5d83edb6b302 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java @@ -0,0 +1,101 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.xmlworkflow.state.actions.processingaction; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.util.Util; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.workflow.WorkflowException; +import org.dspace.xmlworkflow.service.WorkflowRequirementsService; +import org.dspace.xmlworkflow.state.Step; +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; +import org.dspace.xmlworkflow.state.actions.ActionResult; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; + +public class RatingReviewAction extends ProcessingAction implements ActionAdvancedInfo { + + private static final String RATING = "rating"; + + private boolean descriptionRequired; + private int maxValue; + + @Override + public void activate(Context c, XmlWorkflowItem wf) + throws SQLException, IOException, AuthorizeException, WorkflowException { + + } + + @Override + public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) + throws SQLException, AuthorizeException, IOException, WorkflowException { + if (request.getParameter(RATING) != null) { + int rating = Util.getIntParameter(request, "rating"); + //Add our rating to the metadata + itemService.addMetadata(c, wfi.getItem(), WorkflowRequirementsService.WORKFLOW_SCHEMA, "rating", null, null, + String.valueOf(rating)); + itemService.update(c, wfi.getItem()); + + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + } else { + //We have pressed the leave button so return to our submission page + return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); + } + } + + @Override + public List getOptions() { + return Arrays.asList(RATING); + } + + @Override + protected List getAdvancedOptions() { + return Arrays.asList(RATING); + } + + @Override + protected boolean isAdvanced() { + return !getOptions().isEmpty(); + } + + @Override + protected Map getAdvancedInfo() { + Map advancedInfo = super.getAdvancedInfo(); + ActionAdvancedInfo scoreReviewActionAdvancedInfo = new ScoreReviewActionAdvancedInfo(); + scoreReviewActionAdvancedInfo.setMaxValue(getMaxValue()); + scoreReviewActionAdvancedInfo.setDescriptionRequired(isDescriptionRequired()); + advancedInfo.put(RATING, scoreReviewActionAdvancedInfo); + return advancedInfo; + } + + @Override + public boolean isDescriptionRequired() { + return descriptionRequired; + } + + @Override + public void setDescriptionRequired(boolean descriptionRequired) { + this.descriptionRequired = descriptionRequired; + } + + @Override + public int getMaxValue() { + return maxValue; + } + + @Override + public void setMaxValue(int maxValue) { + this.maxValue = maxValue; + } +} diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java new file mode 100644 index 000000000000..84a3a13c6096 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java @@ -0,0 +1,31 @@ +/** + * 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.xmlworkflow.state.actions.processingaction; + +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; + +public class ScoreReviewActionAdvancedInfo implements ActionAdvancedInfo { + private boolean descriptionRequired; + private int maxValue; + + public boolean isDescriptionRequired() { + return descriptionRequired; + } + + public void setDescriptionRequired(boolean descriptionRequired) { + this.descriptionRequired = descriptionRequired; + } + + public int getMaxValue() { + return maxValue; + } + + public void setMaxValue(int maxValue) { + this.maxValue = maxValue; + } +} diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml index 318d1ad3d754..88000896d671 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow-actions.xml @@ -16,6 +16,10 @@ + + + + @@ -63,6 +67,12 @@ + + + + + + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java new file mode 100644 index 000000000000..16909c4352a7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java @@ -0,0 +1,30 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import org.dspace.app.rest.model.ScoreReviewActionAdvancedInfoRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo; + +public class ScoreReviewActionAdvancedInfoConverter + implements DSpaceConverter { + + @Override + public ScoreReviewActionAdvancedInfoRest convert(ScoreReviewActionAdvancedInfo modelObject, Projection projection) { + ScoreReviewActionAdvancedInfoRest restModel = new ScoreReviewActionAdvancedInfoRest(); + restModel.setProjection(projection); + restModel.setDescriptionRequired(modelObject.isDescriptionRequired()); + restModel.setMaxValue(modelObject.getMaxValue()); + return restModel; + } + + @Override + public Class getModelClass() { + return ScoreReviewActionAdvancedInfo.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java index ee6479433e35..07aeef9aa979 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java @@ -26,6 +26,8 @@ public WorkflowActionRest convert(WorkflowActionConfig modelObject, Projection p restModel.setProjection(projection); restModel.setId(modelObject.getId()); restModel.setOptions(modelObject.getOptions()); + restModel.setAdvancedOptions(modelObject.getAdvancedOptions()); + restModel.setAdvancedInfo(modelObject.getAdvancedInfo()); return restModel; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java new file mode 100644 index 000000000000..dd6b3bb6b48a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java @@ -0,0 +1,53 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +public class ScoreReviewActionAdvancedInfoRest extends WorkflowActionRest { + + private boolean descriptionRequired; + private int maxValue; + + /** + * Generic getter for the description required boolean + * + * @return the description required boolean value of this ScoreReviewActionAdvancedInfoRest + */ + public boolean isDescriptionRequired() { + return descriptionRequired; + } + + /** + * Generic setter for the description required boolean + * + * @param descriptionRequired The description required boolean to be set on this ScoreReviewActionAdvancedInfoRest + */ + public void setDescriptionRequired(boolean descriptionRequired) { + this.descriptionRequired = descriptionRequired; + } + + /** + * Generic getter for the max value + * + * @return the max value of this ScoreReviewActionAdvancedInfoRest + */ + public int getMaxValue() { + return maxValue; + } + + /** + * Generic setter for the max value + * + * @param maxValue The max value to be set on this ScoreReviewActionAdvancedInfoRest + */ + public void setMaxValue(int maxValue) { + this.maxValue = maxValue; + } + + + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java index e998df6bc2bc..b1f9677a5452 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java @@ -8,8 +8,10 @@ package org.dspace.app.rest.model; import java.util.List; +import java.util.Map; import org.dspace.app.rest.RestResourceController; +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; /** * The rest resource used for workflow actions @@ -23,6 +25,8 @@ public class WorkflowActionRest extends BaseObjectRest { public static final String NAME_PLURAL = "workflowactions"; private List options; + private List advancedOptions; + private Map advancedInfo; @Override public String getCategory() { @@ -56,4 +60,49 @@ public List getOptions() { public void setOptions(List options) { this.options = options; } + + /** + * Generic getter for the advanced options + * + * @return the advanced options value of this WorkflowActionRest + */ + public List getAdvancedOptions() { + return advancedOptions; + } + + /** + * Generic setter for the advanced options + * + * @param advancedOptions The advanced options to be set on this WorkflowActionRest + */ + public void setAdvancedOptions(List advancedOptions) { + this.advancedOptions = advancedOptions; + } + + /** + * Generic getter for advanced boolean + * + * @return the advanced boolean value of this WorkflowActionRest + */ + public boolean getAdvanced() { + return !advancedOptions.isEmpty(); + } + + /** + * Generic getter for advanced info + * + * @return the advanced info value of this WorkflowActionRest + */ + public Map getAdvancedInfo() { + return advancedInfo; + } + + /** + * Generic setter for advanced info + * + * @param advancedInfo The advanced info to be set on this WorkflowActionRest + */ + public void setAdvancedInfo(Map advancedInfo) { + this.advancedInfo = advancedInfo; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java index de687ebd9d18..51a0a79f29ea 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java @@ -8,11 +8,14 @@ package org.dspace.app.rest; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Map; + import org.dspace.app.rest.matcher.WorkflowActionMatcher; import org.dspace.app.rest.model.WorkflowActionRest; import org.dspace.app.rest.repository.WorkflowActionRestRepository; @@ -82,6 +85,8 @@ public void getWorkflowActionByName_ExistentWithOptions_editaction() throws Exce .andExpect(status().isOk()) // has options .andExpect(jsonPath("$.options", not(empty()))) + .andExpect(jsonPath("$.advancedOptions", not(empty()))) + .andExpect(jsonPath("$.advanced", is(true))) //Matches expected corresponding rest action values .andExpect(jsonPath("$", Matchers.is( WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) @@ -99,6 +104,8 @@ public void getWorkflowActionByName_ExistentWithoutOptions_claimaction() throws .andExpect(status().isOk()) // has no options .andExpect(jsonPath("$.options", empty())) + .andExpect(jsonPath("$.advancedOptions", empty())) + .andExpect(jsonPath("$.advanced", is(false))) //Matches expected corresponding rest action values .andExpect(jsonPath("$", Matchers.is( WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflowNoOptions) @@ -125,4 +132,45 @@ public void getWorkflowActionByName_ExistentWithOptions_NoToken() throws Excepti //We expect a 401 Unauthorized .andExpect(status().isUnauthorized()); } + + @Test + public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameActionWithOptions = "ratingreviewaction"; + WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) + //We expect a 200 is ok status + .andExpect(status().isOk()) + // has options + .andExpect(jsonPath("$.options", not(empty()))) + .andExpect(jsonPath("$.advancedOptions", not(empty()))) + .andExpect(jsonPath("$.advanced", is(true))) + .andExpect(jsonPath("$.advancedInfo", not(empty()))) + //Matches expected corresponding rest action values + .andExpect(jsonPath("$", Matchers.is( + WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) + ))); + } + + @Test + public void getWorkflowActionByName_ExistentWithOptions_scorereviewaction() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameActionWithOptions = "scorereviewaction"; + WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) + //We expect a 200 is ok status + .andExpect(status().isOk()) + // has options + .andExpect(jsonPath("$.options", not(empty()))) + .andExpect(jsonPath("$.advancedOptions", not(empty()))) + .andExpect(jsonPath("$.advanced", is(true))) + .andExpect(jsonPath("$.advancedInfo", is(Map.of()))) + //Matches expected corresponding rest action values + .andExpect(jsonPath("$", Matchers.is( + WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) + ))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java index 69f9c501aa51..749a22fd4e89 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java @@ -32,6 +32,8 @@ public static Matcher matchWorkflowActionEntry(WorkflowActionCon return allOf( hasJsonPath("$.id", is(workflowAction.getId())), hasJsonPath("$.options", is(workflowAction.getOptions())), + hasJsonPath("$.advancedOptions", is(workflowAction.getAdvancedOptions())), + hasJsonPath("$.advanced", is(workflowAction.isAdvanced())), hasJsonPath("$._links.self.href", containsString(WORKFLOW_ACTIONS_ENDPOINT + workflowAction.getId())) ); } diff --git a/dspace/config/spring/api/workflow-actions.xml b/dspace/config/spring/api/workflow-actions.xml index c1252f4b1790..00de634cfa63 100644 --- a/dspace/config/spring/api/workflow-actions.xml +++ b/dspace/config/spring/api/workflow-actions.xml @@ -14,6 +14,10 @@ + + + + @@ -61,6 +65,12 @@ + + + + + + From 014e671fd64d19a343c74a748c05add0a0b7be54 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 19 Dec 2022 10:09:55 -0600 Subject: [PATCH 139/394] Remove unnecessary system.out from IT --- .../test/java/org/dspace/app/rest/VersionRestRepositoryIT.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java index 25cfb7532db9..ed3e811db2ce 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java @@ -877,7 +877,6 @@ public void createNewVersionItemByCollectionAdminTest() throws Exception { .build(); item.setSubmitter(eperson); - System.out.println(item.getSubmitter().getEmail().toString()); context.restoreAuthSystemState(); @@ -895,7 +894,6 @@ public void createNewVersionItemByCollectionAdminTest() throws Exception { hasJsonPath("$.type", is("version")) ))) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - System.out.println("version 2: " + item.getSubmitter().getEmail().toString()); } finally { VersionBuilder.delete(idRef.get()); } From 7efc9554602868565661fecf00de73cd1d7b1c01 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 19 Dec 2022 17:40:18 +0100 Subject: [PATCH 140/394] [8563][DURACOM-101] Updated JavaDoc for `computePattern` method --- .../src/main/java/org/dspace/app/util/RegexPatternUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java index 63657aa25daa..956096d120f7 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java @@ -34,7 +34,9 @@ public class RegexPatternUtils { /** * Computes a pattern starting from a regex definition with flags that - * uses the standard format: /{regex}/{flags}. + * uses the standard format: /{regex}/{flags} (ECMAScript format). + * This method can transform an ECMAScript regex into a java {@code Pattern} object + * wich can be used to validate strings. *
* If regex is null, empty or blank a null {@code Pattern} will be retrieved * If it's a valid regex, then a non-null {@code Pattern} will be retrieved, From 496f936a7d118b2d104ee7a9da9dcf3aace3efa9 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 19 Dec 2022 18:14:15 +0100 Subject: [PATCH 141/394] [8563][DURACOM-101] Removed unreachable branch --- .../dspace/app/util/RegexPatternUtils.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java index 956096d120f7..578e57fb0909 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java +++ b/dspace-api/src/main/java/org/dspace/app/util/RegexPatternUtils.java @@ -51,20 +51,21 @@ public static final Pattern computePattern(String regex) throws PatternSyntaxExc return null; } Matcher inputMatcher = PATTERN_REGEX_INPUT_VALIDATOR.matcher(regex); - Pattern pattern = null; + String regexPattern = regex; + String regexFlags = ""; if (inputMatcher.matches()) { - String regexPattern = inputMatcher.group(2); - String regexFlags = + regexPattern = + Optional.of(inputMatcher.group(2)) + .filter(StringUtils::isNotBlank) + .orElse(regex); + regexFlags = Optional.ofNullable(inputMatcher.group(3)) - .filter(StringUtils::isNotBlank) - .map(flags -> String.format(REGEX_FLAGS, flags)) - .orElse("") - .replaceAll("g", ""); - pattern = Pattern.compile(regexFlags + regexPattern); - } else { - pattern = Pattern.compile(regex); + .filter(StringUtils::isNotBlank) + .map(flags -> String.format(REGEX_FLAGS, flags)) + .orElse("") + .replaceAll("g", ""); } - return pattern; + return Pattern.compile(regexFlags + regexPattern); } private RegexPatternUtils() {} From 14b7ac18b0de281a0be2dc07b7dfcc8198b43a1a Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 19 Dec 2022 18:14:40 +0100 Subject: [PATCH 142/394] [8563][DURACOM-101] Improved Regex Tests --- .../app/util/RegexPatternUtilsTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java index 147144eeecc7..da10d467e472 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java @@ -9,10 +9,13 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import org.dspace.AbstractUnitTest; import org.junit.Test; @@ -158,4 +161,28 @@ public void testWithFuzzyRegex() { matcher = computePattern.matcher("Hello"); assertFalse(matcher.matches()); } + + @Test + public void testInvalidRegex() { + String invalidSensitive = "[a-z+"; + assertThrows(PatternSyntaxException.class, () -> RegexPatternUtils.computePattern(invalidSensitive)); + + String invalidRange = "a{1-"; + assertThrows(PatternSyntaxException.class, () -> RegexPatternUtils.computePattern(invalidRange)); + + String invalidGroupPattern = "(abc"; + assertThrows(PatternSyntaxException.class, () -> RegexPatternUtils.computePattern(invalidGroupPattern)); + + String emptyPattern = ""; + Pattern computePattern = RegexPatternUtils.computePattern(emptyPattern); + assertNull(computePattern); + + String blankPattern = " "; + computePattern = RegexPatternUtils.computePattern(blankPattern); + assertNull(computePattern); + + String nullPattern = null; + computePattern = RegexPatternUtils.computePattern(nullPattern); + assertNull(computePattern); + } } From 72813eebaf53eb07dba3ddda72565d5e79b881da Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 19 Dec 2022 18:40:06 +0100 Subject: [PATCH 143/394] [8563][DURACOM-101] Multiflag regex tests --- .../app/util/RegexPatternUtilsTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java index da10d467e472..30a9100ad4a5 100644 --- a/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java +++ b/dspace-api/src/test/java/org/dspace/app/util/RegexPatternUtilsTest.java @@ -7,6 +7,7 @@ */ package org.dspace.app.util; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -185,4 +186,29 @@ public void testInvalidRegex() { computePattern = RegexPatternUtils.computePattern(nullPattern); assertNull(computePattern); } + + @Test + public void testMultiFlagRegex() { + String multilineSensitive = "/[a-z]+/gi"; + Pattern computePattern = RegexPatternUtils.computePattern(multilineSensitive); + assertNotNull(computePattern); + Matcher matcher = computePattern.matcher("hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("Hello"); + assertTrue(matcher.matches()); + + multilineSensitive = "/[a-z]+/gim"; + computePattern = RegexPatternUtils.computePattern(multilineSensitive); + assertNotNull(computePattern); + matcher = computePattern.matcher("Hello" + System.lineSeparator() + "Everyone"); + assertTrue(matcher.find()); + assertEquals("Hello", matcher.group()); + assertTrue(matcher.find()); + assertEquals("Everyone", matcher.group()); + + matcher = computePattern.matcher("hello"); + assertTrue(matcher.matches()); + matcher = computePattern.matcher("HELLO"); + assertTrue(matcher.matches()); + } } From 507189a828e55c46d4da104283f5e41c28eb4f29 Mon Sep 17 00:00:00 2001 From: Joost Date: Tue, 20 Dec 2022 09:35:59 +0100 Subject: [PATCH 144/394] [task 97298] only check for allowed domains on register --- .../RegistrationRestRepository.java | 14 ++++++-- .../rest/RegistrationRestRepositoryIT.java | 34 ++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java index 2d90a3a93902..74a9c91f7e3a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -12,6 +12,7 @@ import javax.mail.MessagingException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.InternalServerErrorException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; @@ -76,6 +77,7 @@ public Page findAll(Context context, Pageable pageable) { public RegistrationRest createAndReturn(Context context) { HttpServletRequest request = requestService.getCurrentRequest().getHttpServletRequest(); ObjectMapper mapper = new ObjectMapper(); + String type = request.getParameter("type"); RegistrationRest registrationRest; try { ServletInputStream input = request.getInputStream(); @@ -86,6 +88,15 @@ public RegistrationRest createAndReturn(Context context) { if (StringUtils.isBlank(registrationRest.getEmail())) { throw new UnprocessableEntityException("The email cannot be omitted from the Registration endpoint"); } + try { + if (type != null && type.equals("register") && + !authenticationService.canSelfRegister(context, request, registrationRest.getEmail())) { + throw new DSpaceBadRequestException("registration is not allowed with this email address"); + } + } catch (SQLException e) { + log.error("something went wrong while checking if registration is allowed"); + throw new InternalServerErrorException(e); + } EPerson eperson = null; try { eperson = ePersonService.findByEmail(context, registrationRest.getEmail()); @@ -109,9 +120,6 @@ public RegistrationRest createAndReturn(Context context) { throw new AccessDeniedException( "Registration is disabled, you are not authorized to create a new Authorization"); } - if (!authenticationService.canSelfRegister(context, request, registrationRest.getEmail())) { - throw new DSpaceBadRequestException("registration is not allowed with this email address"); - } accountService.sendRegistrationInfo(context, registrationRest.getEmail()); } catch (SQLException | IOException | MessagingException | AuthorizeException e) { log.error("Something went wrong with sending registration info email: " diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java index 29435cb179fa..61480bd06d23 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java @@ -24,6 +24,7 @@ import org.dspace.app.rest.matcher.RegistrationMatcher; import org.dspace.app.rest.model.RegistrationRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.EPersonBuilder; import org.dspace.eperson.RegistrationData; import org.dspace.eperson.dao.RegistrationDataDAO; import org.dspace.services.ConfigurationService; @@ -192,13 +193,44 @@ public void testRegisterDomainNotRegistred() throws Exception { ObjectMapper mapper = new ObjectMapper(); getClient().perform(post("/api/eperson/registrations") + .param("type", "register") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } finally { + registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + Iterator iterator = registrationDataList.iterator(); + while (iterator.hasNext()) { + RegistrationData registrationData = iterator.next(); + registrationDataDAO.delete(context, registrationData); + } + } + } + + @Test + public void testRegisterDomainNotRegisteredMailAddressRegistred() throws Exception { + List registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); + try { + context.turnOffAuthorisationSystem(); + String email = "test@gmail.com"; + EPersonBuilder.createEPerson(context) + .withEmail(email) + .withCanLogin(true) + .build(); + context.restoreAuthSystemState(); + configurationService.setProperty("authentication-password.domain.valid", "test.com"); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(email); + + ObjectMapper mapper = new ObjectMapper(); + getClient().perform(post("/api/eperson/registrations") + .param("type", "register") .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isBadRequest()); registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); assertEquals(0, registrationDataList.size()); } finally { - registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); Iterator iterator = registrationDataList.iterator(); while (iterator.hasNext()) { RegistrationData registrationData = iterator.next(); From 027c79e341672d2c1b392800a72c9b9aa7a57e7b Mon Sep 17 00:00:00 2001 From: Adan Roman Date: Tue, 20 Dec 2022 10:39:30 +0100 Subject: [PATCH 145/394] Sorting constant moved and renamed --- .../java/org/dspace/authorize/AuthorizeServiceImpl.java | 4 ++-- .../main/java/org/dspace/content/CollectionServiceImpl.java | 4 ++-- .../java/org/dspace/content/service/CollectionService.java | 6 +++++- .../org/dspace/content/service/DSpaceObjectService.java | 4 ---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index cb02dfaf0609..5dd491fd4d3e 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -31,7 +31,7 @@ import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.DSpaceObjectService; +import org.dspace.content.service.CollectionService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -879,7 +879,7 @@ public List findAdminAuthorizedCollection(Context context, String qu query = formatCustomQuery(query); DiscoverResult discoverResult = getDiscoverResult(context, query + "search.resourcetype:" + IndexableCollection.TYPE, - offset, limit, DSpaceObjectService.COMMUNITIES_AND_COLLECTIONS_SORT_FIELD, SORT_ORDER.asc); + offset, limit, CollectionService.SOLR_SORT_FIELD, SORT_ORDER.asc); for (IndexableObject solrCollections : discoverResult.getIndexableObjects()) { Collection collection = ((IndexableCollection) solrCollections).getIndexedObject(); collections.add(collection); diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index 00e601248a20..8488b4eaf128 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -947,7 +947,7 @@ public List findCollectionsWithSubmit(String q, Context context, Com discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); - discoverQuery.setSortField(COMMUNITIES_AND_COLLECTIONS_SORT_FIELD, SORT_ORDER.asc); + discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc); DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, null, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); @@ -1027,7 +1027,7 @@ public List findCollectionsWithSubmit(String q, Context context, Com discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); - discoverQuery.setSortField(COMMUNITIES_AND_COLLECTIONS_SORT_FIELD, SORT_ORDER.asc); + discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc); DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, entityType, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 522bdac22480..a5b2b7d8d891 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -33,6 +33,11 @@ public interface CollectionService extends DSpaceObjectService, DSpaceObjectLegacySupportService { + /* + * Field used to sort community and collection lists at solr + */ + public static final String SOLR_SORT_FIELD = "dc.title_sort"; + /** * Create a new collection with a new ID. * Once created the collection is added to the given community @@ -46,7 +51,6 @@ public interface CollectionService public Collection create(Context context, Community community) throws SQLException, AuthorizeException; - /** * Create a new collection with the supplied handle and with a new ID. * Once created the collection is added to the given community diff --git a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java index 31a33885ba73..606f5bb7c02f 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java @@ -52,10 +52,6 @@ public interface DSpaceObjectService { = new MetadataFieldName(DC, "format"); public static final MetadataFieldName MD_SOURCE = new MetadataFieldName(DC, "source"); - /* - * Field used to sort community and collection lists at solr - */ - public static final String COMMUNITIES_AND_COLLECTIONS_SORT_FIELD = "dc.title_sort"; /** * Generic find for when the precise type of an Entity is not known From 167016084d47e72096dfeccddf425d4ae6cd4043 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Tue, 20 Dec 2022 17:14:51 +0100 Subject: [PATCH 146/394] Renaming the resource policy sql file and adding the same file to the H2 so our tests are in sync --- .../V7.5_2022.12.06__index_action_resource_policy.sql} | 0 .../V7.5_2022.12.06__index_action_resource_policy.sql | 9 +++++++++ 2 files changed, 9 insertions(+) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/{postgres/V7.2_2022.12.06__index_action_resource_policy.sql => h2/V7.5_2022.12.06__index_action_resource_policy.sql} (100%) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.12.06__index_action_resource_policy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.06__index_action_resource_policy.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.2_2022.12.06__index_action_resource_policy.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.06__index_action_resource_policy.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.sql new file mode 100644 index 000000000000..696e84433dcd --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.06__index_action_resource_policy.sql @@ -0,0 +1,9 @@ +-- +-- 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/ +-- + +CREATE INDEX resourcepolicy_action_idx ON resourcepolicy(action_id); From 0567e8df0365eba339d915fb5421147f42c3bd43 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Tue, 20 Dec 2022 17:19:44 +0100 Subject: [PATCH 147/394] Adding a sensible default to the "bulkedit.change.commit.count" retrieval --- .../src/main/java/org/dspace/app/bulkedit/MetadataImport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 20a72a6ca6f4..9f9535b8342a 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -598,7 +598,7 @@ public List runImport(Context c, boolean change, changes.add(whatHasChanged); } - if (change && (rowCount % configurationService.getIntProperty("bulkedit.change.commit.count") == 0)) { + if (change && (rowCount % configurationService.getIntProperty("bulkedit.change.commit.count", 100) == 0)) { c.commit(); handler.logInfo(LogHelper.getHeader(c, "metadata_import_commit", "lineNumber=" + rowCount)); } From c21eb479f317d2dafbdbd04e5dbee346bdd45ae3 Mon Sep 17 00:00:00 2001 From: jensroets Date: Wed, 21 Dec 2022 14:05:34 +0100 Subject: [PATCH 148/394] 97414 Advanced workflow actions: refactor for updated rest contract --- .../xmlworkflow/state/actions/Action.java | 8 ++-- .../state/actions/ActionAdvancedInfo.java | 9 +++-- .../state/actions/WorkflowActionConfig.java | 5 +-- .../processingaction/ProcessingAction.java | 8 ---- .../processingaction/RatingReviewAction.java | 31 +++++--------- ...va => RatingReviewActionAdvancedInfo.java} | 29 +++++++++++++- ...tingReviewActionAdvancedInfoConverter.java | 33 +++++++++++++++ ...coreReviewActionAdvancedInfoConverter.java | 30 -------------- .../converter/WorkflowActionConverter.java | 6 ++- ...> RatingReviewActionAdvancedInfoRest.java} | 40 ++++++++++++++++++- .../app/rest/model/WorkflowActionRest.java | 13 +++--- .../rest/WorkflowActionRestRepositoryIT.java | 26 +----------- .../rest/matcher/WorkflowActionMatcher.java | 1 - 13 files changed, 133 insertions(+), 106 deletions(-) rename dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/{ScoreReviewActionAdvancedInfo.java => RatingReviewActionAdvancedInfo.java} (53%) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java rename dspace-server-webapp/src/main/java/org/dspace/app/rest/model/{ScoreReviewActionAdvancedInfoRest.java => RatingReviewActionAdvancedInfoRest.java} (59%) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java index f5e58f2d3d84..b09d6193618c 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java @@ -10,9 +10,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; @@ -40,7 +38,7 @@ public abstract class Action { private WorkflowActionConfig parent; private static final String ERROR_FIELDS_ATTRIBUTE = "dspace.workflow.error_fields"; private List advancedOptions = new ArrayList<>(); - private Map advancedInfo = new HashMap<>(); + private List advancedInfo = new ArrayList<>(); /** * Called when a workflow item becomes eligible for this Action. @@ -202,10 +200,10 @@ protected List getAdvancedOptions() { } protected boolean isAdvanced() { - return !advancedOptions.isEmpty(); + return !getAdvancedOptions().isEmpty(); } - protected Map getAdvancedInfo() { + protected List getAdvancedInfo() { return advancedInfo; } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java index b54d4ebeca1b..c6e68e1f5734 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java @@ -8,8 +8,9 @@ package org.dspace.xmlworkflow.state.actions; public interface ActionAdvancedInfo { - boolean isDescriptionRequired(); - void setDescriptionRequired(boolean descriptionRequired); - int getMaxValue(); - void setMaxValue(int maxValue); + String getType(); + void setType(String type); + String getId(); + void setId(String id); + } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java index 924670ee4a2b..3475b04c7478 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/WorkflowActionConfig.java @@ -8,7 +8,6 @@ package org.dspace.xmlworkflow.state.actions; import java.util.List; -import java.util.Map; import org.dspace.xmlworkflow.state.Step; @@ -72,7 +71,7 @@ public List getOptions() { /** * Returns a list of advanced options this user has on this action, resulting in the next step of the workflow - * @returnA list of advanced options of this action, resulting in the next step of the workflow + * @return A list of advanced options of this action, resulting in the next step of the workflow */ public List getAdvancedOptions() { return this.processingAction.getAdvancedOptions(); @@ -90,7 +89,7 @@ public boolean isAdvanced() { * Returns a Map of info for the advanced options this user has on this action * @return a Map of info for the advanced options this user has on this action */ - public Map getAdvancedInfo() { + public List getAdvancedInfo() { return this.processingAction.getAdvancedInfo(); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java index 585f3b40a28f..8b8358a8d632 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java @@ -8,7 +8,6 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.sql.SQLException; -import java.util.List; import javax.servlet.http.HttpServletRequest; import org.dspace.content.service.ItemService; @@ -49,11 +48,4 @@ public boolean isAuthorized(Context context, HttpServletRequest request, XmlWork task.getStepID().equals(getParent().getStep().getId()) && task.getActionID().equals(getParent().getId()); } - - @Override - protected List getAdvancedOptions() { - List advancedOptions = super.getAdvancedOptions(); - advancedOptions.add(SUBMIT_EDIT_METADATA); - return advancedOptions; - } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java index 5d83edb6b302..5d31054c9b98 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.List; -import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; @@ -24,7 +23,7 @@ import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; -public class RatingReviewAction extends ProcessingAction implements ActionAdvancedInfo { +public class RatingReviewAction extends ProcessingAction { private static final String RATING = "rating"; @@ -66,35 +65,25 @@ protected List getAdvancedOptions() { @Override protected boolean isAdvanced() { - return !getOptions().isEmpty(); + return !getAdvancedOptions().isEmpty(); } @Override - protected Map getAdvancedInfo() { - Map advancedInfo = super.getAdvancedInfo(); - ActionAdvancedInfo scoreReviewActionAdvancedInfo = new ScoreReviewActionAdvancedInfo(); - scoreReviewActionAdvancedInfo.setMaxValue(getMaxValue()); - scoreReviewActionAdvancedInfo.setDescriptionRequired(isDescriptionRequired()); - advancedInfo.put(RATING, scoreReviewActionAdvancedInfo); + protected List getAdvancedInfo() { + List advancedInfo = super.getAdvancedInfo(); + RatingReviewActionAdvancedInfo ratingReviewActionAdvancedInfo = new RatingReviewActionAdvancedInfo(); + ratingReviewActionAdvancedInfo.setDescriptionRequired(descriptionRequired); + ratingReviewActionAdvancedInfo.setMaxValue(maxValue); + ratingReviewActionAdvancedInfo.setType(RATING); + ratingReviewActionAdvancedInfo.setId(RATING); + advancedInfo.add(ratingReviewActionAdvancedInfo); return advancedInfo; } - @Override - public boolean isDescriptionRequired() { - return descriptionRequired; - } - - @Override public void setDescriptionRequired(boolean descriptionRequired) { this.descriptionRequired = descriptionRequired; } - @Override - public int getMaxValue() { - return maxValue; - } - - @Override public void setMaxValue(int maxValue) { this.maxValue = maxValue; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java similarity index 53% rename from dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java rename to dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java index 84a3a13c6096..26f1ddff825e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java @@ -8,10 +8,14 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; +import org.springframework.util.DigestUtils; -public class ScoreReviewActionAdvancedInfo implements ActionAdvancedInfo { +public class RatingReviewActionAdvancedInfo implements ActionAdvancedInfo { private boolean descriptionRequired; private int maxValue; + private String type; + private String id; + public boolean isDescriptionRequired() { return descriptionRequired; @@ -28,4 +32,27 @@ public int getMaxValue() { public void setMaxValue(int maxValue) { this.maxValue = maxValue; } + + @Override + public String getType() { + return type; + } + + @Override + public void setType(String type) { + this.type = "action_info_" + type; + } + + @Override + public String getId() { + return id; + } + + @Override + public void setId(String type) { + String idString = type + + ";descriptionRequired," + descriptionRequired + + ";maxValue," + maxValue; + this.id = DigestUtils.md5DigestAsHex(idString.getBytes()); + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java new file mode 100644 index 000000000000..243435a6e937 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java @@ -0,0 +1,33 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import org.dspace.app.rest.model.RatingReviewActionAdvancedInfoRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.xmlworkflow.state.actions.processingaction.RatingReviewActionAdvancedInfo; + +public class RatingReviewActionAdvancedInfoConverter + implements DSpaceConverter { + + @Override + public RatingReviewActionAdvancedInfoRest convert(RatingReviewActionAdvancedInfo modelObject, + Projection projection) { + RatingReviewActionAdvancedInfoRest restModel = new RatingReviewActionAdvancedInfoRest(); + restModel.setProjection(projection); + restModel.setDescriptionRequired(modelObject.isDescriptionRequired()); + restModel.setMaxValue(modelObject.getMaxValue()); + restModel.setType(modelObject.getType()); + restModel.setId(modelObject.getId()); + return restModel; + } + + @Override + public Class getModelClass() { + return RatingReviewActionAdvancedInfo.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java deleted file mode 100644 index 16909c4352a7..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java +++ /dev/null @@ -1,30 +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.converter; - -import org.dspace.app.rest.model.ScoreReviewActionAdvancedInfoRest; -import org.dspace.app.rest.projection.Projection; -import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo; - -public class ScoreReviewActionAdvancedInfoConverter - implements DSpaceConverter { - - @Override - public ScoreReviewActionAdvancedInfoRest convert(ScoreReviewActionAdvancedInfo modelObject, Projection projection) { - ScoreReviewActionAdvancedInfoRest restModel = new ScoreReviewActionAdvancedInfoRest(); - restModel.setProjection(projection); - restModel.setDescriptionRequired(modelObject.isDescriptionRequired()); - restModel.setMaxValue(modelObject.getMaxValue()); - return restModel; - } - - @Override - public Class getModelClass() { - return ScoreReviewActionAdvancedInfo.class; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java index 07aeef9aa979..f905bbf1b335 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowActionConverter.java @@ -26,8 +26,10 @@ public WorkflowActionRest convert(WorkflowActionConfig modelObject, Projection p restModel.setProjection(projection); restModel.setId(modelObject.getId()); restModel.setOptions(modelObject.getOptions()); - restModel.setAdvancedOptions(modelObject.getAdvancedOptions()); - restModel.setAdvancedInfo(modelObject.getAdvancedInfo()); + if (modelObject.isAdvanced()) { + restModel.setAdvancedOptions(modelObject.getAdvancedOptions()); + restModel.setAdvancedInfo(modelObject.getAdvancedInfo()); + } return restModel; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java similarity index 59% rename from dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java rename to dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java index dd6b3bb6b48a..499fc35be67c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java @@ -7,10 +7,12 @@ */ package org.dspace.app.rest.model; -public class ScoreReviewActionAdvancedInfoRest extends WorkflowActionRest { +public class RatingReviewActionAdvancedInfoRest extends WorkflowActionRest { private boolean descriptionRequired; private int maxValue; + private String type; + private String id; /** * Generic getter for the description required boolean @@ -48,6 +50,42 @@ public void setMaxValue(int maxValue) { this.maxValue = maxValue; } + /** + * Generic getter for the type + * + * @return the type of this ScoreReviewActionAdvancedInfoRest + */ + @Override + public String getType() { + return type; + } + /** + * Generic setter for the type + * + * @param type The type to be set on this ScoreReviewActionAdvancedInfoRest + */ + public void setType(String type) { + this.type = type; + } + /** + * Generic getter for the id + * + * @return the id of this ScoreReviewActionAdvancedInfoRest + */ + @Override + public String getId() { + return id; + } + + /** + * Generic setter for the id + * + * @param id The id to be set on this ScoreReviewActionAdvancedInfoRest + */ + @Override + public void setId(String id) { + this.id = id; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java index b1f9677a5452..0bac2e6fad4a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java @@ -8,8 +8,9 @@ package org.dspace.app.rest.model; import java.util.List; -import java.util.Map; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.apache.commons.collections4.CollectionUtils; import org.dspace.app.rest.RestResourceController; import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; @@ -26,7 +27,7 @@ public class WorkflowActionRest extends BaseObjectRest { private List options; private List advancedOptions; - private Map advancedInfo; + private List advancedInfo; @Override public String getCategory() { @@ -66,6 +67,7 @@ public void setOptions(List options) { * * @return the advanced options value of this WorkflowActionRest */ + @JsonInclude(JsonInclude.Include.NON_NULL) public List getAdvancedOptions() { return advancedOptions; } @@ -85,7 +87,7 @@ public void setAdvancedOptions(List advancedOptions) { * @return the advanced boolean value of this WorkflowActionRest */ public boolean getAdvanced() { - return !advancedOptions.isEmpty(); + return CollectionUtils.isNotEmpty(getAdvancedOptions()); } /** @@ -93,7 +95,8 @@ public boolean getAdvanced() { * * @return the advanced info value of this WorkflowActionRest */ - public Map getAdvancedInfo() { + @JsonInclude(JsonInclude.Include.NON_NULL) + public List getAdvancedInfo() { return advancedInfo; } @@ -102,7 +105,7 @@ public Map getAdvancedInfo() { * * @param advancedInfo The advanced info to be set on this WorkflowActionRest */ - public void setAdvancedInfo(Map advancedInfo) { + public void setAdvancedInfo(List advancedInfo) { this.advancedInfo = advancedInfo; } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java index 51a0a79f29ea..e96220911c87 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java @@ -14,8 +14,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.util.Map; - import org.dspace.app.rest.matcher.WorkflowActionMatcher; import org.dspace.app.rest.model.WorkflowActionRest; import org.dspace.app.rest.repository.WorkflowActionRestRepository; @@ -85,8 +83,7 @@ public void getWorkflowActionByName_ExistentWithOptions_editaction() throws Exce .andExpect(status().isOk()) // has options .andExpect(jsonPath("$.options", not(empty()))) - .andExpect(jsonPath("$.advancedOptions", not(empty()))) - .andExpect(jsonPath("$.advanced", is(true))) + .andExpect(jsonPath("$.advanced", is(false))) //Matches expected corresponding rest action values .andExpect(jsonPath("$", Matchers.is( WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) @@ -104,7 +101,6 @@ public void getWorkflowActionByName_ExistentWithoutOptions_claimaction() throws .andExpect(status().isOk()) // has no options .andExpect(jsonPath("$.options", empty())) - .andExpect(jsonPath("$.advancedOptions", empty())) .andExpect(jsonPath("$.advanced", is(false))) //Matches expected corresponding rest action values .andExpect(jsonPath("$", Matchers.is( @@ -153,24 +149,4 @@ public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() thr ))); } - @Test - public void getWorkflowActionByName_ExistentWithOptions_scorereviewaction() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - String nameActionWithOptions = "scorereviewaction"; - WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); - //When we call this facets endpoint - getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) - //We expect a 200 is ok status - .andExpect(status().isOk()) - // has options - .andExpect(jsonPath("$.options", not(empty()))) - .andExpect(jsonPath("$.advancedOptions", not(empty()))) - .andExpect(jsonPath("$.advanced", is(true))) - .andExpect(jsonPath("$.advancedInfo", is(Map.of()))) - //Matches expected corresponding rest action values - .andExpect(jsonPath("$", Matchers.is( - WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) - ))); - } - } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java index 749a22fd4e89..652cbff5dc62 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java @@ -32,7 +32,6 @@ public static Matcher matchWorkflowActionEntry(WorkflowActionCon return allOf( hasJsonPath("$.id", is(workflowAction.getId())), hasJsonPath("$.options", is(workflowAction.getOptions())), - hasJsonPath("$.advancedOptions", is(workflowAction.getAdvancedOptions())), hasJsonPath("$.advanced", is(workflowAction.isAdvanced())), hasJsonPath("$._links.self.href", containsString(WORKFLOW_ACTIONS_ENDPOINT + workflowAction.getId())) ); From 3cf0be61ef3437e26f623a1f3569eca598f07519 Mon Sep 17 00:00:00 2001 From: jensroets Date: Wed, 21 Dec 2022 16:00:08 +0100 Subject: [PATCH 149/394] 97414 Advanced workflow actions: SelectReviewerAction advanced info --- .../SelectReviewerAction.java | 23 ++++++ .../SelectReviewerActionAdvancedInfo.java | 49 ++++++++++++ ...ctReviewerActionAdvancedInfoConverter.java | 32 ++++++++ .../SelectReviewerActionAdvancedInfoRest.java | 74 +++++++++++++++++++ .../rest/WorkflowActionRestRepositoryIT.java | 20 +++++ 5 files changed, 198 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java index 16a87772755a..43fb00ae0468 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -20,6 +21,7 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.state.Step; +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.WorkflowItemRole; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -110,6 +112,27 @@ public List getOptions() { return options; } + @Override + protected List getAdvancedOptions() { + return Arrays.asList(SUBMIT_SELECT_REVIEWER); + } + + @Override + protected boolean isAdvanced() { + return !getAdvancedOptions().isEmpty(); + } + + @Override + protected List getAdvancedInfo() { + List advancedInfo = super.getAdvancedInfo(); + SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo(); + selectReviewerActionAdvancedInfo.setRole(role); + selectReviewerActionAdvancedInfo.setType(SUBMIT_SELECT_REVIEWER); + selectReviewerActionAdvancedInfo.setId(SUBMIT_SELECT_REVIEWER); + advancedInfo.add(selectReviewerActionAdvancedInfo); + return advancedInfo; + } + public Role getRole() { return role; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java new file mode 100644 index 000000000000..c9fc4ebc82a2 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.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.xmlworkflow.state.actions.processingaction; + +import org.dspace.xmlworkflow.Role; +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; +import org.springframework.util.DigestUtils; + +public class SelectReviewerActionAdvancedInfo implements ActionAdvancedInfo { + private Role role; + private String type; + private String id; + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; + } + + @Override + public String getType() { + return type; + } + + @Override + public void setType(String type) { + this.type = "action_info_" + type; + } + + @Override + public String getId() { + return id; + } + + @Override + public void setId(String type) { + String idString = type + + ";role," + role; + this.id = DigestUtils.md5DigestAsHex(idString.getBytes()); + } +} + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java new file mode 100644 index 000000000000..50d5c180a0d6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java @@ -0,0 +1,32 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import org.dspace.app.rest.model.SelectReviewerActionAdvancedInfoRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo; + +public class SelectReviewerActionAdvancedInfoConverter + implements DSpaceConverter { + + @Override + public SelectReviewerActionAdvancedInfoRest convert(SelectReviewerActionAdvancedInfo modelObject, + Projection projection) { + SelectReviewerActionAdvancedInfoRest restModel = new SelectReviewerActionAdvancedInfoRest(); + restModel.setProjection(projection); + restModel.setRole(modelObject.getRole()); + restModel.setType(modelObject.getType()); + restModel.setId(modelObject.getId()); + return restModel; + } + + @Override + public Class getModelClass() { + return SelectReviewerActionAdvancedInfo.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java new file mode 100644 index 000000000000..1fa209bb3bfa --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.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.model; + +import org.dspace.xmlworkflow.Role; + +public class SelectReviewerActionAdvancedInfoRest extends WorkflowActionRest { + + private Role role; + private String type; + private String id; + + /** + * Generic getter for the role + * + * @return the role value of this SelectReviewerActionAdvancedInfoRest + */ + public Role getRole() { + return role; + } + + /** + * Generic setter for the role + * + * @param role The role to be set on this SelectReviewerActionAdvancedInfoRest + */ + public void setRole(Role role) { + this.role = role; + } + + /** + * Generic getter for the type + * + * @return the type of this SelectReviewerActionAdvancedInfoRest + */ + @Override + public String getType() { + return type; + } + + /** + * Generic setter for the type + * + * @param type The type to be set on this SelectReviewerActionAdvancedInfoRest + */ + public void setType(String type) { + this.type = type; + } + + /** + * Generic getter for the id + * + * @return the id of this SelectReviewerActionAdvancedInfoRest + */ + @Override + public String getId() { + return id; + } + + /** + * Generic setter for the id + * + * @param id The id to be set on this SelectReviewerActionAdvancedInfoRest + */ + @Override + public void setId(String id) { + this.id = id; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java index e96220911c87..cd0de21e00d3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java @@ -149,4 +149,24 @@ public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() thr ))); } + @Test + public void getWorkflowActionByName_ExistentWithOptions_selectrevieweraction() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + String nameActionWithOptions = "selectrevieweraction"; + WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); + //When we call this facets endpoint + getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) + //We expect a 200 is ok status + .andExpect(status().isOk()) + // has options + .andExpect(jsonPath("$.options", not(empty()))) + .andExpect(jsonPath("$.advancedOptions", not(empty()))) + .andExpect(jsonPath("$.advanced", is(true))) + .andExpect(jsonPath("$.advancedInfo", not(empty()))) + //Matches expected corresponding rest action values + .andExpect(jsonPath("$", Matchers.is( + WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) + ))); + } + } From 26f0a42521a378148ddb2cc6a9c1a550db8674cc Mon Sep 17 00:00:00 2001 From: Philipp Rumpf Date: Thu, 22 Dec 2022 12:42:23 +0000 Subject: [PATCH 150/394] Merge pull request #8 * DataCite: use configuration service to set URL + timeout * DataCite: configure DataCite url + timeout in external-providers.cfg * DataCite: add DataCite bean to default configuration --- .../DataCiteImportMetadataSourceServiceImpl.java | 8 +++++--- dspace/config/modules/external-providers.cfg | 8 +++++++- dspace/config/spring/api/external-services.xml | 13 ++++++++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java index f19a406736b8..a11f2bc2471d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/datacite/DataCiteImportMetadataSourceServiceImpl.java @@ -29,6 +29,7 @@ import org.dspace.importer.external.service.AbstractImportMetadataSourceService; import org.dspace.importer.external.service.DoiCheck; import org.dspace.importer.external.service.components.QuerySource; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -49,9 +50,8 @@ public class DataCiteImportMetadataSourceServiceImpl @Autowired private LiveImportClient liveImportClient; - private final int timeoutMs = 180000; - - private final String url = "https://api.datacite.org/dois/"; + @Autowired + private ConfigurationService configurationService; @Override public String getImportSource() { @@ -95,6 +95,8 @@ public Collection getRecords(String query, int start, int count) t id = query; } uriParameters.put("query", id); + int timeoutMs = configurationService.getIntProperty("datacite.timeout", 180000); + String url = configurationService.getProperty("datacite.url", "https://api.datacite.org/dois/"); String responseString = liveImportClient.executeHttpGetRequest(timeoutMs, url, params); JsonNode jsonNode = convertStringJsonToJsonNode(responseString); if (jsonNode == null) { diff --git a/dspace/config/modules/external-providers.cfg b/dspace/config/modules/external-providers.cfg index 1ae7d5a12bac..b7c0e120dbcd 100644 --- a/dspace/config/modules/external-providers.cfg +++ b/dspace/config/modules/external-providers.cfg @@ -85,4 +85,10 @@ scopus.search-api.viewMode = wos.apiKey = wos.url = https://wos-api.clarivate.com/api/wos/id/ wos.url.search = https://wos-api.clarivate.com/api/wos/?databaseId=WOS&lang=en&usrQuery= -################################################################## \ No newline at end of file +################################################################# +#------------------------- DataCite ----------------------------# +#---------------------------------------------------------------# + +datacite.url = https://api.datacite.org/dois/ +datacite.timeout = 180000 +################################################################# diff --git a/dspace/config/spring/api/external-services.xml b/dspace/config/spring/api/external-services.xml index 2a9f601b527c..6d7d50c39f1b 100644 --- a/dspace/config/spring/api/external-services.xml +++ b/dspace/config/spring/api/external-services.xml @@ -212,4 +212,15 @@
- \ No newline at end of file + + + + + + + Publication + none + + + + From dea4b5e17fcda3aa9d732f09f29a33587597f50b Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Thu, 22 Dec 2022 17:40:43 +0200 Subject: [PATCH 151/394] [CST-7754] LYRASIS: Supervisor orders (REST). --- .../exception/ResourceConflictException.java | 39 + ...ServiceSupervisionOrderIndexingPlugin.java | 72 ++ ...iceWorkspaceWorkflowRestrictionPlugin.java | 13 +- .../InprogressSubmissionIndexFactoryImpl.java | 16 + .../dspace/supervision/SupervisionOrder.java | 78 ++ .../SupervisionOrderServiceImpl.java | 126 +++ .../supervision/dao/SupervisionOrderDao.java | 32 + .../dao/impl/SupervisionOrderDaoImpl.java | 59 ++ .../enumeration/SupervisionOrderType.java | 17 + .../SupervisionOrderServiceFactory.java | 29 + .../SupervisionOrderServiceFactoryImpl.java | 28 + .../service/SupervisionOrderService.java | 32 + ...5_2022.12.09__Supervision_Orders_table.sql | 20 + ...5_2022.12.09__Supervision_Orders_table.sql | 20 + ...5_2022.12.09__Supervision_Orders_table.sql | 20 + .../org/dspace/builder/AbstractBuilder.java | 6 + .../builder/SupervisionOrderBuilder.java | 94 ++ .../converter/SupervisionOrderConverter.java | 60 ++ .../DSpaceApiExceptionControllerAdvice.java | 19 + .../app/rest/model/SupervisionOrderRest.java | 72 ++ .../app/rest/model/WorkspaceItemRest.java | 8 + .../hateoas/SupervisionOrderResource.java | 25 + .../SupervisionOrderRestRepository.java | 219 ++++ ...ceItemSupervisionOrdersLinkRepository.java | 61 ++ ...WorkflowRestPermissionEvaluatorPlugin.java | 9 + ...paceItemRestPermissionEvaluatorPlugin.java | 11 + .../java/org/dspace/app/rest/utils/Utils.java | 4 + .../app/rest/DiscoveryRestControllerIT.java | 491 +++++++++ .../SupervisionOrderRestRepositoryIT.java | 932 ++++++++++++++++++ .../rest/WorkflowItemRestRepositoryIT.java | 69 ++ .../rest/WorkspaceItemRestRepositoryIT.java | 275 ++++++ .../app/rest/matcher/FacetEntryMatcher.java | 11 + .../app/rest/matcher/FacetValueMatcher.java | 12 + .../rest/matcher/SupervisionOrderMatcher.java | 39 + dspace/config/hibernate.cfg.xml | 2 + .../config/spring/api/core-dao-services.xml | 2 + .../spring/api/core-factory-services.xml | 2 + dspace/config/spring/api/core-services.xml | 2 + dspace/config/spring/api/discovery.xml | 186 ++++ 39 files changed, 3210 insertions(+), 2 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java create mode 100644 dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java create mode 100644 dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java create mode 100644 dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java create mode 100644 dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java create mode 100644 dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.java create mode 100644 dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql create mode 100644 dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.java diff --git a/dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java b/dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java new file mode 100644 index 000000000000..ef0dd116d915 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java @@ -0,0 +1,39 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.exception; + +/** + * This class provides an exception to be used when a conflict on a resource + * occurs. + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + * + */ +public class ResourceConflictException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final Object resource; + + /** + * Create a ResourceConflictException with a message and the conflicting + * resource. + * + * @param message the error message + * @param resource the resource that caused the conflict + */ + public ResourceConflictException(String message, Object resource) { + super(message); + this.resource = resource; + } + + public Object getResource() { + return resource; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java new file mode 100644 index 000000000000..2ed19954e6da --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java @@ -0,0 +1,72 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.discovery; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.solr.common.SolrInputDocument; +import org.dspace.content.Item; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.discovery.indexobject.IndexableInProgressSubmission; +import org.dspace.discovery.indexobject.IndexableWorkflowItem; +import org.dspace.discovery.indexobject.IndexableWorkspaceItem; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; +import org.dspace.workflow.WorkflowItemService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SolrServiceSupervisionOrderIndexingPlugin implements SolrServiceIndexPlugin { + + @Autowired(required = true) + private WorkspaceItemService workspaceItemService; + + @Autowired(required = true) + private WorkflowItemService workflowItemService; + + @Autowired(required = true) + private SupervisionOrderService supervisionOrderService; + + @Override + public void additionalIndex(Context context, IndexableObject indexableObject, SolrInputDocument document) { + try { + + if (!(indexableObject instanceof IndexableWorkspaceItem) && + !(indexableObject instanceof IndexableWorkflowItem)) { + return; + } + + Item item = + (((IndexableInProgressSubmission) indexableObject).getIndexedObject()).getItem(); + + if (Objects.isNull(item)) { + return; + } + addSupervisedField(context, item, document); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + private void addSupervisedField(Context context, Item item, SolrInputDocument document) throws SQLException { + List supervisionOrders = supervisionOrderService.findByItem(context, item); + if (CollectionUtils.isNotEmpty(supervisionOrders)) { + document.addField("supervised", true); + } else { + document.addField("supervised", false); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java index fd05be1cb521..161849475651 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceWorkspaceWorkflowRestrictionPlugin.java @@ -40,6 +40,11 @@ public class SolrServiceWorkspaceWorkflowRestrictionPlugin implements SolrServic */ public static final String DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME = "workflowAdmin"; + /** + * The name of the discover configuration used by administrators to search for workspace and workflow tasks + */ + public static final String DISCOVER_SUPERVISION_CONFIGURATION_NAME = "supervision"; + @Autowired(required = true) protected GroupService groupService; @@ -60,18 +65,22 @@ public void additionalSearchParameters( ); boolean isWorkflowAdmin = isAdmin(context) && DISCOVER_WORKFLOW_ADMIN_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName()); + + boolean isSupervision = + DISCOVER_SUPERVISION_CONFIGURATION_NAME.equals(discoveryQuery.getDiscoveryConfigurationName()); + EPerson currentUser = context.getCurrentUser(); // extra security check to avoid the possibility that an anonymous user // get access to workspace or workflow - if (currentUser == null && (isWorkflow || isWorkspace)) { + if (currentUser == null && (isWorkflow || isWorkspace || isSupervision)) { throw new IllegalStateException( "An anonymous user cannot perform a workspace or workflow search"); } if (isWorkspace) { // insert filter by submitter solrQuery.addFilterQuery("submitter_authority:(" + currentUser.getID() + ")"); - } else if (isWorkflow && !isWorkflowAdmin) { + } else if ((isWorkflow && !isWorkflowAdmin) || (isSupervision && !isAdmin(context))) { // Retrieve all the groups the current user is a member of ! Set groups; try { diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java index d0b0f363e64b..8a24b997ffae 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/InprogressSubmissionIndexFactoryImpl.java @@ -22,6 +22,8 @@ import org.dspace.discovery.indexobject.factory.InprogressSubmissionIndexFactory; import org.dspace.discovery.indexobject.factory.ItemIndexFactory; import org.dspace.eperson.EPerson; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.util.SolrUtils; import org.dspace.workflow.WorkflowItem; import org.springframework.beans.factory.annotation.Autowired; @@ -39,6 +41,9 @@ public abstract class InprogressSubmissionIndexFactoryImpl @Autowired protected ItemIndexFactory indexableItemService; + @Autowired + protected SupervisionOrderService supervisionOrderService; + @Override public SolrInputDocument buildDocument(Context context, T indexableObject) throws SQLException, IOException { @@ -60,6 +65,8 @@ public void storeInprogressItemFields(Context context, SolrInputDocument doc, submitter.getFullName()); } + addSupervisedByFacetIndex(context, item, doc); + doc.addField("inprogress.item", new IndexableItem(inProgressSubmission.getItem()).getUniqueIndexID()); // get the location string (for searching by collection & community) @@ -82,4 +89,13 @@ public void storeInprogressItemFields(Context context, SolrInputDocument doc, indexableItemService.addDiscoveryFields(doc, context, item, discoveryConfigurations); indexableCollectionService.storeCommunityCollectionLocations(doc, locations); } + + private void addSupervisedByFacetIndex(Context context, Item item, SolrInputDocument doc) throws SQLException { + List supervisionOrders = supervisionOrderService.findByItem(context, item); + for (SupervisionOrder supervisionOrder : supervisionOrders) { + addFacetIndex(doc, "supervisedBy", supervisionOrder.getGroup().getID().toString(), + supervisionOrder.getGroup().getName()); + } + + } } diff --git a/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java new file mode 100644 index 000000000000..52d5dacb74bb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrder.java @@ -0,0 +1,78 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.core.ReloadableEntity; +import org.dspace.eperson.Group; +import org.dspace.supervision.service.SupervisionOrderService; + +/** + * Database entity representation of the supervision_orders table + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Entity +@Table(name = "supervision_orders") +public class SupervisionOrder implements ReloadableEntity { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "supervision_orders_seq") + @SequenceGenerator(name = "supervision_orders_seq", sequenceName = "supervision_orders_seq", allocationSize = 1) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id") + private Item item; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "eperson_group_id") + private Group group; + + /** + * Protected constructor, create object using: + * {@link SupervisionOrderService#create(Context, Item, Group)} + */ + protected SupervisionOrder() { + + } + + @Override + public Integer getID() { + return id; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.java b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.java new file mode 100644 index 000000000000..21a54f085f61 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/SupervisionOrderServiceImpl.java @@ -0,0 +1,126 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.event.Event; +import org.dspace.supervision.dao.SupervisionOrderDao; +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link SupervisionOrderService} + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderServiceImpl implements SupervisionOrderService { + + @Autowired(required = true) + private SupervisionOrderDao supervisionDao; + + @Autowired(required = true) + private GroupService groupService; + + @Autowired(required = true) + private ItemService itemService; + + protected SupervisionOrderServiceImpl() { + + } + + @Override + public SupervisionOrder create(Context context) throws SQLException, AuthorizeException { + return supervisionDao.create(context, new SupervisionOrder()); + } + + @Override + public SupervisionOrder find(Context context, int id) throws SQLException { + return supervisionDao.findByID(context, SupervisionOrder.class, id); + } + + @Override + public void update(Context context, SupervisionOrder supervisionOrder) + throws SQLException, AuthorizeException { + supervisionDao.save(context, supervisionOrder); + } + + @Override + public void update(Context context, List supervisionOrders) + throws SQLException, AuthorizeException { + if (CollectionUtils.isNotEmpty(supervisionOrders)) { + for (SupervisionOrder supervisionOrder : supervisionOrders) { + supervisionDao.save(context, supervisionOrder); + } + } + } + + @Override + public void delete(Context context, SupervisionOrder supervisionOrder) throws SQLException, AuthorizeException { + supervisionDao.delete(context, supervisionOrder); + } + + @Override + public SupervisionOrder create(Context context, Item item, Group group) throws SQLException { + SupervisionOrder supervisionOrder = new SupervisionOrder(); + supervisionOrder.setItem(item); + supervisionOrder.setGroup(group); + SupervisionOrder supOrder = supervisionDao.create(context, supervisionOrder); + context.addEvent(new Event(Event.MODIFY, Constants.ITEM, item.getID(), null, + itemService.getIdentifiers(context, item))); + return supOrder; + } + + @Override + public List findAll(Context context) throws SQLException { + return supervisionDao.findAll(context, SupervisionOrder.class); + } + + @Override + public List findByItem(Context context, Item item) throws SQLException { + return supervisionDao.findByItem(context, item); + } + + @Override + public SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException { + return supervisionDao.findByItemAndGroup(context, item, group); + } + + @Override + public boolean isSupervisor(Context context, EPerson ePerson, Item item) throws SQLException { + List supervisionOrders = findByItem(context, item); + + if (CollectionUtils.isEmpty(supervisionOrders)) { + return false; + } + + return supervisionOrders + .stream() + .map(SupervisionOrder::getGroup) + .anyMatch(group -> isMember(context, ePerson, group)); + } + + private boolean isMember(Context context, EPerson ePerson, Group group) { + try { + return groupService.isMember(context, ePerson, group); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java b/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java new file mode 100644 index 000000000000..185e9d72a6af --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java @@ -0,0 +1,32 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; + +/** + * Database Access Object interface class for the SupervisionOrder object. + * + * The implementation of this class is responsible for all database calls for the SupervisionOrder object + * and is autowired by spring + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public interface SupervisionOrderDao extends GenericDAO { + + List findByItem(Context context, Item item) throws SQLException; + SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException; + +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java b/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java new file mode 100644 index 000000000000..09cd0841e78f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/dao/impl/SupervisionOrderDaoImpl.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision.dao.impl; + +import java.sql.SQLException; +import java.util.List; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.dspace.content.Item; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.SupervisionOrder_; +import org.dspace.supervision.dao.SupervisionOrderDao; + +/** + * Hibernate implementation of the Database Access Object interface class for the SupervisionOrder object. + * This class is responsible for all database calls for the SupervisionOrder object + * and is autowired by spring + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderDaoImpl extends AbstractHibernateDAO implements SupervisionOrderDao { + + @Override + public List findByItem(Context context, Item item) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SupervisionOrder.class); + + Root supervisionOrderRoot = criteriaQuery.from(SupervisionOrder.class); + criteriaQuery.select(supervisionOrderRoot); + criteriaQuery.where(criteriaBuilder.equal(supervisionOrderRoot.get(SupervisionOrder_.item), item)); + + return list(context, criteriaQuery, false, SupervisionOrder.class, -1, -1); + } + + @Override + public SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SupervisionOrder.class); + + Root supervisionOrderRoot = criteriaQuery.from(SupervisionOrder.class); + criteriaQuery.select(supervisionOrderRoot); + criteriaQuery.where(criteriaBuilder.and( + criteriaBuilder.equal(supervisionOrderRoot.get(SupervisionOrder_.item), item), + criteriaBuilder.equal(supervisionOrderRoot.get(SupervisionOrder_.group), group) + )); + + return singleResult(context, criteriaQuery); + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java new file mode 100644 index 000000000000..698e9d234efb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java @@ -0,0 +1,17 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision.enumeration; + +/** + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public enum SupervisionOrderType { + OBSERVER, + EDITOR +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.java b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.java new file mode 100644 index 000000000000..8577ee8b1613 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactory.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision.factory; + +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.service.SupervisionOrderService; + +/** + * Abstract factory to get services for the supervision package, + * use SupervisionOrderServiceFactory.getInstance() to retrieve an implementation + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public abstract class SupervisionOrderServiceFactory { + + public abstract SupervisionOrderService getSupervisionOrderService(); + + public static SupervisionOrderServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance() + .getServiceManager() + .getServiceByName("supervisionOrderServiceFactory", + SupervisionOrderServiceFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.java new file mode 100644 index 000000000000..407a79c6899d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/factory/SupervisionOrderServiceFactoryImpl.java @@ -0,0 +1,28 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision.factory; + +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Factory implementation to get services for the supervision package, + * use SupervisionOrderServiceFactory.getInstance() to retrieve an implementation + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderServiceFactoryImpl extends SupervisionOrderServiceFactory { + + @Autowired(required = true) + private SupervisionOrderService supervisionOrderService; + + @Override + public SupervisionOrderService getSupervisionOrderService() { + return supervisionOrderService; + } +} diff --git a/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java new file mode 100644 index 000000000000..f99090d6e600 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java @@ -0,0 +1,32 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.service.DSpaceCRUDService; +import org.dspace.supervision.SupervisionOrder; + +/** + * Service interface class for the SupervisionOrder object. + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public interface SupervisionOrderService extends DSpaceCRUDService { + + SupervisionOrder create(Context context, Item item, Group group) throws SQLException; + List findAll(Context context) throws SQLException; + List findByItem(Context context, Item item) throws SQLException; + SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException; + boolean isSupervisor(Context context, EPerson ePerson, Item item) throws SQLException; +} diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql new file mode 100644 index 000000000000..33d3eb5c82c8 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -0,0 +1,20 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +------------------------------------------------------------------------------- +-- Table to store supervision orders +------------------------------------------------------------------------------- + +CREATE TABLE supervision_orders +( + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + eperson_group_id UUID REFERENCES epersongroup(uuid) ON DELETE CASCADE +); + +CREATE SEQUENCE supervision_orders_seq; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql new file mode 100644 index 000000000000..33d3eb5c82c8 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -0,0 +1,20 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +------------------------------------------------------------------------------- +-- Table to store supervision orders +------------------------------------------------------------------------------- + +CREATE TABLE supervision_orders +( + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + eperson_group_id UUID REFERENCES epersongroup(uuid) ON DELETE CASCADE +); + +CREATE SEQUENCE supervision_orders_seq; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql new file mode 100644 index 000000000000..33d3eb5c82c8 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -0,0 +1,20 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +------------------------------------------------------------------------------- +-- Table to store supervision orders +------------------------------------------------------------------------------- + +CREATE TABLE supervision_orders +( + id INTEGER PRIMARY KEY, + item_id UUID REFERENCES Item(uuid) ON DELETE CASCADE, + eperson_group_id UUID REFERENCES epersongroup(uuid) ON DELETE CASCADE +); + +CREATE SEQUENCE supervision_orders_seq; diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java index 3306ced8f434..df3310cd246f 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -49,6 +49,8 @@ import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.service.ProcessService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.factory.SupervisionOrderServiceFactory; +import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; import org.dspace.versioning.service.VersioningService; @@ -102,6 +104,8 @@ public abstract class AbstractBuilder { static OrcidHistoryService orcidHistoryService; static OrcidQueueService orcidQueueService; static OrcidTokenService orcidTokenService; + static SupervisionOrderService supervisionOrderService; + protected Context context; @@ -161,6 +165,7 @@ public static void init() { orcidHistoryService = OrcidServiceFactory.getInstance().getOrcidHistoryService(); orcidQueueService = OrcidServiceFactory.getInstance().getOrcidQueueService(); orcidTokenService = OrcidServiceFactory.getInstance().getOrcidTokenService(); + supervisionOrderService = SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService(); } @@ -194,6 +199,7 @@ public static void destroy() { requestItemService = null; versioningService = null; orcidTokenService = null; + supervisionOrderService = null; } diff --git a/dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.java new file mode 100644 index 000000000000..849e4cd4ffb5 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/SupervisionOrderBuilder.java @@ -0,0 +1,94 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.builder; + +import java.sql.SQLException; +import java.util.Objects; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; + +/** + * Abstract builder to construct SupervisionOrder Objects + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderBuilder + extends AbstractBuilder { + + private static final Logger log = LogManager.getLogger(SupervisionOrderBuilder.class); + + private SupervisionOrder supervisionOrder; + + protected SupervisionOrderBuilder(Context context) { + super(context); + } + + public static SupervisionOrderBuilder createSupervisionOrder(Context context, Item item, Group group) { + SupervisionOrderBuilder builder = new SupervisionOrderBuilder(context); + return builder.create(context, item, group); + } + + private SupervisionOrderBuilder create(Context context, Item item, Group group) { + try { + this.context = context; + this.supervisionOrder = getService().create(context, item, group); + } catch (Exception e) { + log.error("Error in SupervisionOrderBuilder.create(..), error: ", e); + } + return this; + } + + @Override + public void cleanup() throws Exception { + delete(supervisionOrder); + } + + @Override + public SupervisionOrder build() throws SQLException, AuthorizeException { + try { + getService().update(context, supervisionOrder); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + log.error("Error in SupervisionOrderBuilder.build(), error: ", e); + } + return supervisionOrder; + } + + @Override + public void delete(Context context, SupervisionOrder supervisionOrder) throws Exception { + if (Objects.nonNull(supervisionOrder)) { + getService().delete(context, supervisionOrder); + } + } + + @Override + protected SupervisionOrderService getService() { + return supervisionOrderService; + } + + private void delete(SupervisionOrder supervisionOrder) throws Exception { + try (Context context = new Context()) { + context.turnOffAuthorisationSystem(); + context.setDispatcher("noindex"); + SupervisionOrder attached = context.reloadEntity(supervisionOrder); + if (attached != null) { + getService().delete(context, attached); + } + context.complete(); + indexingService.commit(); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.java new file mode 100644 index 000000000000..e9ffb224468b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SupervisionOrderConverter.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import java.util.Objects; + +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +/** + * This class is responsible to convert SupervisionOrder to its rest model + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Component +public class SupervisionOrderConverter + implements DSpaceConverter { + + @Lazy + @Autowired + private ConverterService converter; + + @Override + public SupervisionOrderRest convert(SupervisionOrder modelObject, Projection projection) { + + SupervisionOrderRest rest = new SupervisionOrderRest(); + Item item = modelObject.getItem(); + Group group = modelObject.getGroup(); + + rest.setId(modelObject.getID()); + + if (Objects.nonNull(item)) { + rest.setItem(converter.toRest(item, projection)); + } + + if (Objects.nonNull(group)) { + rest.setGroup(converter.toRest(group, projection)); + } + + rest.setProjection(projection); + + return rest; + } + + @Override + public Class getModelClass() { + return SupervisionOrder.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java index 1cbfd5c63256..8ac1e7fe063d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java @@ -22,13 +22,19 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.exception.ResourceAlreadyExistsException; +import org.dspace.app.exception.ResourceConflictException; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.InvalidReCaptchaException; import org.dspace.orcid.exception.OrcidValidationException; import org.dspace.services.ConfigurationService; import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.repository.support.QueryMethodParameterConversionException; import org.springframework.http.HttpHeaders; @@ -60,6 +66,13 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionHandler { private static final Logger log = LogManager.getLogger(); + @Autowired + @Lazy + private ConverterService converterService; + + @Autowired + private Utils utils; + /** * Default collection of HTTP error codes to log as ERROR with full stack trace. */ @@ -235,6 +248,12 @@ protected void handleGenericException(HttpServletRequest request, HttpServletRes } + @ExceptionHandler(ResourceConflictException.class) + protected ResponseEntity resourceConflictException(ResourceConflictException ex) { + RestModel resource = converterService.toRest(ex.getResource(), utils.obtainProjection()); + return new ResponseEntity(resource, HttpStatus.CONFLICT); + } + /** * Send the error to the response. * 5xx errors will be logged as ERROR with a full stack trace. 4xx errors diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java new file mode 100644 index 000000000000..e114fdeb39f2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SupervisionOrderRest.java @@ -0,0 +1,72 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; +import org.dspace.supervision.SupervisionOrder; + +/** + * The REST Resource of {@link SupervisionOrder}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class SupervisionOrderRest extends BaseObjectRest { + + public static final String NAME = "supervisionorder"; + public static final String CATEGORY = RestAddressableModel.CORE; + + private Integer id; + + @JsonIgnore + private ItemRest item; + + @JsonIgnore + private GroupRest group; + + @Override + public Integer getId() { + return id; + } + + @Override + public void setId(Integer id) { + this.id = id; + } + + public ItemRest getItem() { + return item; + } + + public void setItem(ItemRest item) { + this.item = item; + } + + public GroupRest getGroup() { + return group; + } + + public void setGroup(GroupRest group) { + this.group = group; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java index 69fefd1a9b7e..57a5ab5c7f0e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java @@ -14,10 +14,18 @@ * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@LinksRest(links = { + @LinkRest( + name = WorkspaceItemRest.SUPERVISION_ORDERS, + method = "getSupervisionOrders" + ) +}) public class WorkspaceItemRest extends AInprogressSubmissionRest { public static final String NAME = "workspaceitem"; public static final String CATEGORY = RestAddressableModel.SUBMISSION; + public static final String SUPERVISION_ORDERS = "supervisionOrders"; + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java new file mode 100644 index 000000000000..06439f29efe4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SupervisionOrderResource.java @@ -0,0 +1,25 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * SupervisionOrder Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@RelNameDSpaceResource(SupervisionOrderRest.NAME) +public class SupervisionOrderResource extends DSpaceResource { + public SupervisionOrderResource(SupervisionOrderRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java new file mode 100644 index 000000000000..7d7ede818902 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java @@ -0,0 +1,219 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import static org.dspace.authorize.ResourcePolicy.TYPE_SUBMISSION; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.exception.ResourceConflictException; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.MissingParameterException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.enumeration.SupervisionOrderType; +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible to manage SupervisionOrderRest object + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Component(SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.NAME) +public class SupervisionOrderRestRepository extends DSpaceRestRepository { + + private static final Logger log = + org.apache.logging.log4j.LogManager.getLogger(SupervisionOrderRestRepository.class); + + @Autowired + private SupervisionOrderService supervisionOrderService; + + @Autowired + private ConverterService converterService; + + @Autowired + private ItemService itemService; + + @Autowired + private GroupService groupService; + + @Autowired + private AuthorizeService authorizeService; + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + public SupervisionOrderRest findOne(Context context, Integer id) { + try { + SupervisionOrder supervisionOrder = supervisionOrderService.find(context, id); + if (Objects.isNull(supervisionOrder)) { + throw new ResourceNotFoundException("Couldn't find supervision order for id: " + id); + } + return converterService.toRest(supervisionOrder, utils.obtainProjection()); + } catch (SQLException e) { + log.error("Something went wrong with getting supervision order with id:" + id, e); + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + public Page findAll(Context context, Pageable pageable) { + try { + List supervisionOrders = supervisionOrderService.findAll(context); + return converterService.toRestPage(supervisionOrders, pageable, utils.obtainProjection()); + } catch (SQLException e) { + log.error("Something went wrong with getting supervision orders", e); + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + public SupervisionOrderRest createAndReturn(Context context) throws AuthorizeException, SQLException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + SupervisionOrder supervisionOrder; + String itemId = req.getParameter("uuid"); + String groupId = req.getParameter("group"); + String type = req.getParameter("type"); + + validateParameters(itemId, groupId, type); + + Item item = itemService.find(context, UUID.fromString(itemId)); + if (item == null) { + throw new UnprocessableEntityException("Item with uuid: " + itemId + " not found"); + } + + Group group = groupService.find(context, UUID.fromString(groupId)); + if (group == null) { + throw new UnprocessableEntityException("Group with uuid: " + groupId + " not found"); + } + + supervisionOrder = supervisionOrderService.findByItemAndGroup(context, item, group); + if (Objects.nonNull(supervisionOrder)) { + throw new ResourceConflictException( + "There is a conflict supervision order with itemId <" + itemId + "> and groupId <" + groupId + ">", + supervisionOrder + ); + } + supervisionOrder = supervisionOrderService.create(context, item, group); + addGroupPoliciesToItem(context, item, group, type); + return converterService.toRest(supervisionOrder, utils.obtainProjection()); + } + + @PreAuthorize("hasAuthority('ADMIN')") + @Override + protected void delete(Context context, Integer id) + throws AuthorizeException, RepositoryMethodNotImplementedException { + + try { + SupervisionOrder supervisionOrder = supervisionOrderService.find(context, id); + if (Objects.isNull(supervisionOrder)) { + throw new ResourceNotFoundException( + SupervisionOrderRest.CATEGORY + "." + SupervisionOrderRest.NAME + + " with id: " + id + " not found" + ); + } + supervisionOrderService.delete(context, supervisionOrder); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @PreAuthorize("hasAuthority('ADMIN')") + @SearchRestMethod(name = "byItem") + public Page findByItem(@Parameter(value = "uuid", required = true) String itemId, + Pageable pageable) { + try { + Context context = obtainContext(); + Item item = itemService.find(context, UUID.fromString(itemId)); + if (Objects.isNull(item)) { + throw new ResourceNotFoundException("no item is found for the uuid < " + itemId + " >"); + } + return converterService.toRestPage(supervisionOrderService.findByItem(context, item), + pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public Class getDomainClass() { + return SupervisionOrderRest.class; + } + + private void validateParameters(String itemId, String groupId, String type) { + if (Objects.isNull(itemId)) { + throw new MissingParameterException("Missing item (uuid) parameter"); + } + + if (Objects.isNull(groupId)) { + throw new MissingParameterException("Missing group (uuid) parameter"); + } + + if (Objects.isNull(type)) { + throw new MissingParameterException("Missing type parameter"); + } else if (!type.equals(SupervisionOrderType.EDITOR.toString()) && + !type.equals(SupervisionOrderType.OBSERVER.toString())) { + throw new IllegalArgumentException("wrong type value, Type must be (EDITOR or OBSERVER)"); + } + + } + + private void addGroupPoliciesToItem(Context context, Item item, Group group, String type) + throws SQLException, AuthorizeException { + + if (StringUtils.isNotEmpty(type)) { + if (type.equals("EDITOR")) { + addGroupPolicyToItem(context, item, Constants.READ, group, TYPE_SUBMISSION); + addGroupPolicyToItem(context, item, Constants.WRITE, group, TYPE_SUBMISSION); + } else if (type.equals("OBSERVER")) { + addGroupPolicyToItem(context, item, Constants.READ, group, TYPE_SUBMISSION); + } + } + } + + private void addGroupPolicyToItem(Context context, Item item, int action, Group group, String policyType) + throws AuthorizeException, SQLException { + authorizeService.addPolicy(context, item, action, group, policyType); + List bundles = item.getBundles(); + for (Bundle bundle : bundles) { + authorizeService.addPolicy(context, bundle, action, group, policyType); + List bits = bundle.getBitstreams(); + for (Bitstream bitstream : bits) { + authorizeService.addPolicy(context, bitstream, action, group, policyType); + } + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java new file mode 100644 index 000000000000..e0d57ae0de52 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemSupervisionOrdersLinkRepository.java @@ -0,0 +1,61 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.SupervisionOrderRest; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.supervision.service.SupervisionOrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for the supervision orders of an WorkspaceItem + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +@Component(WorkspaceItemRest.CATEGORY + "." + WorkspaceItemRest.NAME + "." + WorkspaceItemRest.SUPERVISION_ORDERS) +public class WorkspaceItemSupervisionOrdersLinkRepository + extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + WorkspaceItemService workspaceItemService; + + @Autowired + SupervisionOrderService supervisionOrderService; + + @PreAuthorize("hasAuthority('ADMIN')") + public Page getSupervisionOrders(@Nullable HttpServletRequest request, + Integer id, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + WorkspaceItem workspaceItem = workspaceItemService.find(context, id); + if (workspaceItem == null) { + throw new ResourceNotFoundException("No such workspace item: " + id); + } + return converter.toRestPage( + supervisionOrderService.findByItem(context, workspaceItem.getItem()), + optionalPageable, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java index e225df067da4..626290fdc3bb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkflowRestPermissionEvaluatorPlugin.java @@ -20,6 +20,7 @@ import org.dspace.eperson.service.EPersonService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; +import org.dspace.supervision.service.SupervisionOrderService; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.dspace.xmlworkflow.storedcomponents.service.ClaimedTaskService; import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; @@ -56,6 +57,9 @@ public class WorkflowRestPermissionEvaluatorPlugin extends RestObjectPermissionE @Autowired private EPersonService ePersonService; + @Autowired + private SupervisionOrderService supervisionOrderService; + @Override public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, DSpaceRestPermission permission) { @@ -89,6 +93,11 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t if (claimedTaskService.findByWorkflowIdAndEPerson(context, workflowItem, ePerson) != null) { return true; } + + if (supervisionOrderService.isSupervisor(context, ePerson, workflowItem.getItem())) { + return true; + } + } catch (SQLException | AuthorizeException | IOException e) { log.error(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java index 9a8de3675c6b..1b1a604b2094 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java @@ -19,6 +19,7 @@ import org.dspace.eperson.EPerson; import org.dspace.services.RequestService; import org.dspace.services.model.Request; +import org.dspace.supervision.service.SupervisionOrderService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -41,6 +42,9 @@ public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermis @Autowired WorkspaceItemService wis; + @Autowired + private SupervisionOrderService supervisionOrderService; + @Override public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, DSpaceRestPermission permission) { @@ -82,6 +86,13 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t return true; } } + + if (witem.getItem() != null) { + if (supervisionOrderService.isSupervisor(context, ePerson, witem.getItem())) { + return true; + } + } + } catch (SQLException e) { log.error(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index 237550326f46..88278b531c14 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -64,6 +64,7 @@ import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.SupervisionOrderRest; import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.model.VocabularyRest; import org.dspace.app.rest.model.hateoas.EmbeddedPage; @@ -296,6 +297,9 @@ public static String makeSingular(String modelPlural) { if (StringUtils.equals(modelPlural, "orcidhistories")) { return OrcidHistoryRest.NAME; } + if (StringUtils.equals(modelPlural, "supervisionorders")) { + return SupervisionOrderRest.NAME; + } return modelPlural.replaceAll("s$", ""); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index b604bcd9587c..740c9739b86e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -9,11 +9,13 @@ import static com.google.common.net.UrlEscapers.urlPathSegmentEscaper; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.FacetValueMatcher.entrySupervisedBy; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -48,6 +50,7 @@ import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.PoolTaskBuilder; +import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; @@ -63,6 +66,7 @@ import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.supervision.SupervisionOrder; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; @@ -5987,4 +5991,491 @@ public void discoverFacetsTestWithDsoTypeTest() throws Exception { .andExpect(jsonPath("$._embedded.values").value(Matchers.hasSize(1))); } + + @Test + public void discoverFacetsSupervisedByTest() throws Exception { + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + //2. Two workspace items + WorkspaceItem wsItem1 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + //3. Two groups + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + //4. Four supervision orders + SupervisionOrder supervisionOrderOne = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupB).build(); + + SupervisionOrder supervisionOrderThree = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderFour = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupB).build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + //The Admin user browses this endpoint to find the supervisedBy results by the facet + getClient(getAuthToken(admin.getEmail(), password)).perform(get("/api/discover/facets/supervisedBy") + .param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //The name has to be 'supervisedBy' as that's the facet that we've called + .andExpect(jsonPath("$.name", is("supervisedBy"))) + //The facetType has to be `authority` because that's the default configuration for this facet + .andExpect(jsonPath("$.facetType", equalTo("authority"))) + //There always needs to be a self link available + .andExpect(jsonPath("$._links.self.href", + containsString("api/discover/facets/supervisedBy?configuration=supervision"))) + //This is how the page object must look like because it's the default with size 20 + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntry(0, 20)))) + //The supervisedBy values need to be as specified below + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + entrySupervisedBy(groupA.getName(), groupA.getID().toString(), 2), + entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2) + ))); + } + + @Test + public void discoverFacetsSupervisedByWithPrefixTest() throws Exception { + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and one collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + //2. Two workspace items + WorkspaceItem wsItem1 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOneA = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderOneB = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupB).build(); + + SupervisionOrder supervisionOrderTwoA = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupA).build(); + + SupervisionOrder supervisionOrderTwoB = + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupB).build(); + + context.restoreAuthSystemState(); + + //** WHEN ** + //The Admin user browses this endpoint to find the supervisedBy results by the facet + getClient(getAuthToken(admin.getEmail(), password)).perform(get("/api/discover/facets/supervisedBy") + .param("configuration", "supervision") + .param("prefix", "group B")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //The name has to be 'supervisedBy' as that's the facet that we've called + .andExpect(jsonPath("$.name", is("supervisedBy"))) + //The facetType has to be `authority` because that's the default configuration for this facet + .andExpect(jsonPath("$.facetType", equalTo("authority"))) + //There always needs to be a self link available + .andExpect(jsonPath("$._links.self.href", + containsString("api/discover/facets/supervisedBy?prefix=group%2520B&configuration=supervision"))) + //This is how the page object must look like because it's the default with size 20 + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntry(0, 20)))) + //The supervisedBy values need to be as specified below + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2) + ))); + } + + @Test + /** + * This test is intent to verify that tasks are only visible to the admin users + * + * @throws Exception + */ + public void discoverSearchObjectsSupervisionConfigurationTest() throws Exception { + + //We turn off the authorization system in order to create the structure defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + // 1. Two reviewers and two users and two groups + EPerson reviewer1 = + EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + EPerson reviewer2 = + EPersonBuilder.createEPerson(context) + .withEmail("reviewer2@example.com") + .withPassword(password) + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@test.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + // 2. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .build(); + + // the second collection has two workflow steps active + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withWorkflowGroup(1, admin, reviewer1) + .withWorkflowGroup(2, reviewer2) + .build(); + + // 2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withAuthor("Testing, Works") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Test 2") + .withIssueDate("1990-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withAuthor("Testing, Works") + .withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2010-02-13") + .withAuthor("Smith, Maria") + .withAuthor("Doe, Jane") + .withAuthor("test,test") + .withAuthor("test2, test2") + .withAuthor("Maybe, Maybe") + .withSubject("AnotherTest") + .withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + //3. three inprogress submission from a normal user (2 ws, 1 wf that will produce also a pooltask) + context.setCurrentUser(eperson); + WorkspaceItem wsItem1 = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace Item 1") + .withIssueDate("2010-07-23") + .build(); + + WorkspaceItem wsItem2 = WorkspaceItemBuilder.createWorkspaceItem(context, col2) + .withTitle("Workspace Item 2") + .withIssueDate("2010-11-03") + .build(); + + XmlWorkflowItem wfItem1 = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withTitle("Workflow Item 1") + .withIssueDate("2010-11-03") + .build(); + + // create Four supervision orders for above items + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1.getItem(), groupA).build(); + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1.getItem(), groupB).build(); + + // 4. a claimed task from the administrator + ClaimedTask cTask = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Claimed Item") + .withIssueDate("2010-11-03") + .build(); + + // 5. other inprogress submissions made by the administrator + context.setCurrentUser(admin); + WorkspaceItem wsItem1Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withIssueDate("2010-07-23") + .withTitle("Admin Workspace Item 1").build(); + + WorkspaceItem wsItem2Admin = WorkspaceItemBuilder.createWorkspaceItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workspace Item 2").build(); + + XmlWorkflowItem wfItem1Admin = WorkflowItemBuilder.createWorkflowItem(context, col2) + .withIssueDate("2010-11-03") + .withTitle("Admin Workflow Item 1").build(); + + // create Four supervision orders for above items + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem1Admin.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wsItem2Admin.getItem(), groupA).build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1Admin.getItem(), groupA).build(); + SupervisionOrderBuilder.createSupervisionOrder(context, wfItem1Admin.getItem(), groupB).build(); + + // 6. a pool taks in the second step of the workflow + ClaimedTask cTask2 = ClaimedTaskBuilder.createClaimedTask(context, col2, admin).withTitle("Pool Step2 Item") + .withIssueDate("2010-11-04") + .build(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + String adminToken = getAuthToken(admin.getEmail(), password); + String reviewer1Token = getAuthToken(reviewer1.getEmail(), password); + String reviewer2Token = getAuthToken(reviewer2.getEmail(), password); + + getClient(adminToken).perform(post("/api/workflow/claimedtasks/" + cTask2.getID()) + .param("submit_approve", "true") + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .andExpect(status().isNoContent()); + + context.restoreAuthSystemState(); + + // summary of the structure, we have: + // a simple collection + // a second collection with 2 workflow steps that have 1 reviewer each (reviewer1 and reviewer2) + // 3 public items + // 2 workspace items submitted by a regular submitter + // 2 workspace items submitted by the admin + // 4 workflow items: + // 1 pool task in step 1, submitted by the same regular submitter + // 1 pool task in step 1, submitted by the admin + // 1 claimed task in the first workflow step from the repository admin + // 1 pool task task in step 2, from the repository admin + // (This one is created by creating a claimed task for step 1 and approving it) + + //** WHEN ** + // the submitter should not see anything in the workflow configuration + getClient(epersonToken) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + + // reviewer1 should not see pool items, as it is not an administrator + getClient(reviewer1Token) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", + containsString("/api/discover/search/objects"))); + + // admin should see seven pool items and a claimed task + // Three pool items from the submitter and Five from the admin + getClient(adminToken) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 8) + ))) + // These search results have to be shown in the embedded.objects section: + // three workflow items and one claimed task. + // For step 1 one submitted by the user and one submitted by the admin and one for step 2. + //Seeing as everything fits onto one page, they have to all be present + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Admin Workflow Item 1", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Pool Step2 Item", "2010-11-04"))) + ), + Matchers.allOf( + SearchResultMatcher.match("workflow", "workflowitem", "workflowitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "Claimed Item", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem1, + "Workspace Item 1", "2010-07-23"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem2, + "Workspace Item 2", "2010-11-03"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem1Admin, + "Admin Workspace Item 1", "2010-07-23"))) + ), + Matchers.allOf( + SearchResultMatcher.match("submission", "workspaceitem", "workspaceitems"), + JsonPathMatchers.hasJsonPath("$._embedded.indexableObject", + is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssued(wsItem2Admin, + "Admin Workspace Item 2", "2010-11-03"))) + ) + ))) + //These facets have to show up in the embedded.facets section as well with the given hasMore + //property because we don't exceed their default limit for a hasMore true (the default is 10) + .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.resourceTypeFacet(false), + FacetEntryMatcher.typeFacet(false), + FacetEntryMatcher.dateIssuedFacet(false), + FacetEntryMatcher.submitterFacet(false), + FacetEntryMatcher.supervisedByFacet(false) + ))) + //check supervisedBy Facet values + .andExpect(jsonPath("$._embedded.facets[4]._embedded.values", + contains( + entrySupervisedBy(groupA.getName(), groupA.getID().toString(), 6), + entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + + // reviewer2 should not see pool items, as it is not an administrator + getClient(reviewer2Token) + .perform(get("/api/discover/search/objects").param("configuration", "supervision")) + //** THEN ** + //The status has to be 200 OK + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.type", is("discover"))) + //There needs to be a page object that shows the total pages and total elements as well as the + // size and the current page (number) + .andExpect(jsonPath("$._embedded.searchResult.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0) + ))) + //There always needs to be a self link + .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java new file mode 100644 index 000000000000..7bd2553c9b97 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -0,0 +1,932 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; +import static com.jayway.jsonpath.JsonPath.read; +import static org.dspace.app.rest.matcher.ItemMatcher.matchItemWithTitleAndDateIssued; +import static org.dspace.app.rest.matcher.SupervisionOrderMatcher.matchSuperVisionOrder; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import javax.ws.rs.core.MediaType; + +import org.dspace.app.rest.model.patch.AddOperation; +import org.dspace.app.rest.model.patch.ReplaceOperation; +import org.dspace.app.rest.repository.SupervisionOrderRestRepository; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.builder.SupervisionOrderBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test against class {@link SupervisionOrderRestRepository}. + * + * @author Mohamed Eskander (mohamed.eskander at 4science.it) + */ +public class SupervisionOrderRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private SupervisionOrderService supervisionOrderService; + + @Test + public void findAllByAnonymousUserTest() throws Exception { + getClient().perform(get("/api/core/supervisionorders/")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findAllByNotAdminUserTest() throws Exception { + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/core/supervisionorders/")) + .andExpect(status().isForbidden()); + } + + @Test + public void findAllByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOne = + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupA) + .build(); + + SupervisionOrder supervisionOrderTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, item, groupB) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.supervisionorders", + containsInAnyOrder( + matchSuperVisionOrder(supervisionOrderOne), + matchSuperVisionOrder(supervisionOrderTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString("/api/core/supervisionorders"))); + } + + @Test + public void findOneByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void findOneByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", matchSuperVisionOrder(supervisionOrder))); + } + + @Test + public void findOneByAdminAndItemIsWithdrawnTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String patchBody = getPatchContent(List.of(new ReplaceOperation("/withdrawn", true))); + + String adminToken = getAuthToken(admin.getEmail(), password); + // withdraw item + getClient(adminToken).perform(patch("/api/core/items/" + item.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(item.getID().toString()))) + .andExpect(jsonPath("$.withdrawn", Matchers.is(true))) + .andExpect(jsonPath("$.inArchive", Matchers.is(false))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", matchSuperVisionOrder(context.reloadEntity(supervisionOrder)))); + + } + + @Test + public void findOneByAdminButNotFoundTest() throws Exception { + int fakeId = 12354326; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/supervisionorders/" + fakeId)) + .andExpect(status().isNotFound()); + } + + @Test + public void findByItemByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, item, group).build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByItemByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrderBuilder.createSupervisionOrder(context, item, group).build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isForbidden()); + } + + @Test + public void findByItemByAdminButNotFoundItemTest() throws Exception { + String fakeItemId = "d9dcf4c3-093d-413e-a538-93d8589d3ea6"; + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", fakeItemId)) + .andExpect(status().isNotFound()); + } + + @Test + public void findByItemByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOne = + SupervisionOrderBuilder.createSupervisionOrder(context, itemOne, groupA) + .build(); + + SupervisionOrder supervisionOrderTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, itemOne, groupB) + .build(); + + Item itemTwo = + ItemBuilder.createItem(context, col1) + .withTitle("item two title") + .build(); + + SupervisionOrder supervisionOrderItemTwo = + SupervisionOrderBuilder.createSupervisionOrder(context, itemTwo, groupA) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", itemOne.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.supervisionorders", containsInAnyOrder( + matchSuperVisionOrder(supervisionOrderOne), + matchSuperVisionOrder(supervisionOrderTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/core/supervisionorders/search/byItem?uuid=" + itemOne.getID())) + ); + + getClient(adminToken).perform(get("/api/core/supervisionorders/search/byItem") + .param("uuid", itemTwo.getID().toString())) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.supervisionorders", contains( + matchSuperVisionOrder(supervisionOrderItemTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/core/supervisionorders/search/byItem?uuid=" + itemTwo.getID())) + ); + } + + @Test + public void createByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", group.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void createByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", group.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isForbidden()); + } + + @Test + public void createByAdminButMissingParametersTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createByAdminButIncorrectTypeParameterTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", group.getID().toString()) + .param("type", "WRONG") + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createByAdminButNotFoundItemOrGroupTest() throws Exception { + + String fakeItemId = "d9dcf4c3-093d-413e-a538-93d8589d3ea6"; + String fakeGroupId = "d9dcf4c3-093d-413e-a538-93d8589d3ea6"; + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", fakeItemId) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", fakeGroupId) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createTheSameSupervisionOrderTwiceTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .withIssueDate("2017-10-17") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isConflict()); + } + + @Test + public void createByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test1@email.com") + .withPassword(password) + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test2@email.com") + .withPassword(password) + .build(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .withIssueDate("2017-10-17") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + context.restoreAuthSystemState(); + + AtomicInteger supervisionOrderIdOne = new AtomicInteger(); + AtomicInteger supervisionOrderIdTwo = new AtomicInteger(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> supervisionOrderIdOne + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> supervisionOrderIdTwo + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + SupervisionOrder supervisionOrderOne = + supervisionOrderService.find(context, supervisionOrderIdOne.get()); + + SupervisionOrder supervisionOrderTwo = + supervisionOrderService.find(context, supervisionOrderIdTwo.get()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrderOne.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + matchSuperVisionOrder(context.reloadEntity(supervisionOrderOne)))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrderTwo.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + matchSuperVisionOrder(context.reloadEntity(supervisionOrderTwo)))); + + String authTokenA = getAuthToken(userA.getEmail(), password); + String authTokenB = getAuthToken(userB.getEmail(), password); + + String patchBody = getPatchContent(List.of( + new AddOperation("/metadata/dc.title", List.of(Map.of("value", "new title"))) + )); + + // update title of itemOne by userA is Ok + getClient(authTokenA).perform(patch("/api/core/items/" + itemOne.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", is( + matchItemWithTitleAndDateIssued(context.reloadEntity(itemOne), + "new title", "2017-10-17") + ))); + + // update title of itemOne by userB is Forbidden + getClient(authTokenB).perform(patch("/api/core/items/" + itemOne.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteByAnonymousUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(delete("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteByNotAdminUserTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + getClient(authToken).perform(delete("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteByAdminButNotFoundTest() throws Exception { + int fakeId = 12354326; + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(delete("/api/core/supervisionorders/" + fakeId)) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteByAdminTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNotFound()); + } + +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index 025a5f15a999..0bf9cd5297c8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -46,6 +46,7 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; @@ -55,6 +56,7 @@ import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.state.Step; @@ -2123,4 +2125,71 @@ public void whenWorkspaceitemBecomeWorkflowitemWithAccessConditionsTheItemMustBe } } + @Test + public void testSupervisorFindOne() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson user = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("user@test.com") + .withPassword(password) + .build(); + + EPerson anotherUser = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("anotheruser@test.com") + .withPassword(password) + .build(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Community child1 = + CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withWorkflowGroup(1, admin) + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(user) + .build(); + + XmlWorkflowItem workflowItem = + WorkflowItemBuilder.createWorkflowItem(context, collection) + .withSubmitter(admin) + .withTitle("Workflow Item") + .withIssueDate("2017-10-17") + .withAuthor("Author one") + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", workflowItem.getItem().getID().toString()) + .param("group", group.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient(getAuthToken(anotherUser.getEmail(), password)) + .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().isForbidden()); + + getClient(getAuthToken(user.getEmail(), password)) + .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().isOk()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 4a9f03ffff9c..6c975264257a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -11,9 +11,12 @@ import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist; +import static org.dspace.app.rest.matcher.SupervisionOrderMatcher.matchSuperVisionOrder; import static org.dspace.authorize.ResourcePolicy.TYPE_CUSTOM; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -70,6 +73,7 @@ import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -89,6 +93,7 @@ import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; import org.dspace.services.ConfigurationService; +import org.dspace.supervision.SupervisionOrder; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -8291,4 +8296,274 @@ public void sherpaPolicySectionWithWrongIssnCacheTest() throws Exception { assertTrue(date.equals(date2)); } + @Test + public void supervisionOrdersLinksTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace Item 1") + .withIssueDate("2022-12-12") + .withAuthor("Eskander, Mohamed") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + SupervisionOrder supervisionOrderOne = SupervisionOrderBuilder + .createSupervisionOrder(context, witem.getItem(), group) + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken) + .perform(get("/api/submission/workspaceitems/" + witem.getID())).andExpect(status().isOk()) + .andExpect(jsonPath("$", + Matchers.is(WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject(witem, + "Workspace Item 1", "2022-12-12", "ExtraEntry")))) + .andExpect(jsonPath("$._links.supervisionOrders.href", containsString( + "/api/submission/workspaceitems/" + witem.getID() + "/supervisionOrders") + )); + } + + @Test + public void supervisionOrdersEndpointTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + WorkspaceItem witemOne = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Test item -- supervision orders") + .withIssueDate("2022-12-12") + .withAuthor("Eskander, Mohamed") + .grantLicense() + .build(); + + WorkspaceItem witemTwo = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Test item -- no supervision orders") + .withIssueDate("2022-12-12") + .withAuthor("Eskander") + .grantLicense() + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(admin) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrderOne = SupervisionOrderBuilder + .createSupervisionOrder(context, witemOne.getItem(), groupA) + .build(); + + SupervisionOrder supervisionOrderTwo = SupervisionOrderBuilder + .createSupervisionOrder(context, witemOne.getItem(), groupB) + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + String authToken = getAuthToken(eperson.getEmail(), password); + + // Item's supervision orders endpoint of itemOne by not admin + getClient(authToken) + .perform(get("/api/submission/workspaceitems/" + witemOne.getID() + "/supervisionOrders")) + .andExpect(status().isForbidden()); + + // Item's supervision orders endpoint of itemOne by admin + getClient(adminToken) + .perform(get("/api/submission/workspaceitems/" + witemOne.getID() + "/supervisionOrders")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.supervisionOrders", containsInAnyOrder( + matchSuperVisionOrder(supervisionOrderOne), + matchSuperVisionOrder(supervisionOrderTwo) + ))) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/submission/workspaceitems/" + witemOne.getID() + "/supervisionOrders") + )); + + // Item's supervision orders endpoint of itemTwo by admin + getClient(adminToken) + .perform(get("/api/submission/workspaceitems/" + witemTwo.getID() + "/supervisionOrders")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded.supervisionOrders", empty())) + .andExpect(jsonPath("$._links.self.href", containsString( + "/api/submission/workspaceitems/" + witemTwo.getID() + "/supervisionOrders") + )); + } + + @Test + public void patchBySupervisorTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@test.com") + .withPassword(password) + .build(); + + EPerson userC = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userC@test.com") + .withPassword(password) + .build(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isCreated()); + + String authTokenA = getAuthToken(userA.getEmail(), password); + String authTokenB = getAuthToken(userB.getEmail(), password); + String authTokenC = getAuthToken(userC.getEmail(), password); + + getClient(authTokenC).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isForbidden()); + + // a simple patch to update an existent metadata + List updateTitle = new ArrayList<>(); + Map value = new HashMap<>(); + value.put("value", "New Title"); + updateTitle.add(new ReplaceOperation("/sections/traditionalpageone/dc.title/0", value)); + String patchBody = getPatchContent(updateTitle); + + getClient(authTokenB).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + // check the new title and untouched values + Matchers.is(WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry")))); + + getClient(authTokenA).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + Matchers.is( + WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry") + ))); + + getClient(authTokenB).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + Matchers.is( + WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry") + ))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java index 5e3c477506b9..82bedf4a9278 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java @@ -67,6 +67,17 @@ public static Matcher submitterFacet(boolean hasNext) { ); } + public static Matcher supervisedByFacet(boolean hasNext) { + return allOf( + hasJsonPath("$.name", is("supervisedBy")), + hasJsonPath("$.facetType", is("authority")), + hasJsonPath("$.facetLimit", any(Integer.class)), + hasJsonPath("$._links.self.href", containsString("api/discover/facets/supervisedBy")), + hasJsonPath("$._links", matchNextLink(hasNext, "api/discover/facets/supervisedBy")) + + ); + } + public static Matcher dateIssuedFacet(boolean hasNext) { return allOf( hasJsonPath("$.name", is("dateIssued")), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java index 2c7cebdbe973..eff068ea7b0f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetValueMatcher.java @@ -97,4 +97,16 @@ public static Matcher entryDateIssuedWithLabel(String label) { hasJsonPath("$._links.search.href", containsString(",equals")) ); } + + public static Matcher entrySupervisedBy(String label, String authority, int count) { + return allOf( + hasJsonPath("$.authorityKey", is(authority)), + hasJsonPath("$.count", is(count)), + hasJsonPath("$.label", is(label)), + hasJsonPath("$.type", is("discover")), + hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")), + hasJsonPath("$._links.search.href", containsString("f.supervisedBy=" + authority + ",authority")) + ); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.java new file mode 100644 index 000000000000..1ba147879d00 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SupervisionOrderMatcher.java @@ -0,0 +1,39 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.GroupMatcher.matchGroupEntry; +import static org.dspace.app.rest.matcher.ItemMatcher.matchItemProperties; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.dspace.eperson.Group; +import org.dspace.supervision.SupervisionOrder; +import org.hamcrest.Matcher; + +/** + * Utility class to construct a Matcher for an SupervisionOrder object + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderMatcher { + + private SupervisionOrderMatcher() { + } + + public static Matcher matchSuperVisionOrder(SupervisionOrder supervisionOrder) { + Group group = supervisionOrder.getGroup(); + return allOf( + hasJsonPath("$.id", is(supervisionOrder.getID())), + hasJsonPath("$._embedded.item", matchItemProperties(supervisionOrder.getItem())), + hasJsonPath("$._embedded.group", matchGroupEntry(group.getID(), group.getName())) + ); + } + +} diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 085ed0bd6e2c..7017db0dba4c 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -93,5 +93,7 @@ + + diff --git a/dspace/config/spring/api/core-dao-services.xml b/dspace/config/spring/api/core-dao-services.xml index ae4b5e6e3b6b..c5e2353170ae 100644 --- a/dspace/config/spring/api/core-dao-services.xml +++ b/dspace/config/spring/api/core-dao-services.xml @@ -64,5 +64,7 @@ + + diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index 20e5297b6cdb..cef906adc842 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -55,4 +55,6 @@ + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index a124ec830f0c..f7ddd90bc6e7 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -148,5 +148,7 @@ + + diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 57f4c07aee5c..c3606c407d2f 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -40,6 +40,9 @@ + + + @@ -61,10 +64,14 @@ + + + + @@ -894,6 +901,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:WorkspaceItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:WorkspaceItem OR search.resourcetype:XmlWorkflowItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2792,6 +2949,18 @@ + + + + + + + placeholder.placeholder.placeholder + + + + @@ -2866,4 +3035,21 @@ + + + + + + + + + + + + + + + + + From 45e4748482dd10dbdb73f6511ccc4ed7747b59b0 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 23 Dec 2022 10:35:35 +0100 Subject: [PATCH 152/394] 97425: Create "System Wide Alerts" feature --- .../org/dspace/alerts/AllowSessionsEnum.java | 46 +++ .../org/dspace/alerts/SystemWideAlert.java | 179 +++++++++++ .../alerts/SystemWideAlertServiceImpl.java | 110 +++++++ .../dspace/alerts/dao/SystemWideAlertDAO.java | 45 +++ .../dao/impl/SystemWideAlertDAOImpl.java | 48 +++ .../service/SystemWideAlertService.java | 118 +++++++ .../V7.4_2022.12.15__system_wide_alerts.sql | 22 ++ .../V7.4_2022.12.15__system_wide_alerts.sql | 22 ++ .../V7.4_2022.12.15__system_wide_alerts.sql | 22 ++ .../converter/SystemWideAlertConverter.java | 39 +++ .../app/rest/model/SystemWideAlertRest.java | 88 ++++++ .../hateoas/SystemWideAlertResource.java | 23 ++ .../SystemWideAlertRestRepository.java | 164 ++++++++++ .../rest/SystemWideAlertRestRepositoryIT.java | 293 ++++++++++++++++++ dspace/config/hibernate.cfg.xml | 1 + .../config/spring/api/core-dao-services.xml | 2 + dspace/config/spring/api/core-services.xml | 2 + 17 files changed, 1224 insertions(+) create mode 100644 dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java create mode 100644 dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java create mode 100644 dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/alerts/dao/SystemWideAlertDAO.java create mode 100644 dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java create mode 100644 dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.4_2022.12.15__system_wide_alerts.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.4_2022.12.15__system_wide_alerts.sql create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.4_2022.12.15__system_wide_alerts.sql create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SystemWideAlertResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java diff --git a/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java new file mode 100644 index 000000000000..313923bcde2d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.alerts; + +/** + * Enum representing the options for allowing sessions + */ +public enum AllowSessionsEnum { + ALL(0), + CURRENT(1), + ADMIN(2); + + private int allowSessionsType; + + AllowSessionsEnum(int allowSessionsType) { + this.allowSessionsType = allowSessionsType; + } + + public int getValue() { + return allowSessionsType; + } + + public static AllowSessionsEnum fromInt(Integer alertAllowSessionType) { + if (alertAllowSessionType == null) { + return AllowSessionsEnum.ALL; + } + + switch (alertAllowSessionType) { + case 0: + return AllowSessionsEnum.ALL; + case 1: + return AllowSessionsEnum.CURRENT; + case 2: + return AllowSessionsEnum.ADMIN; + default: + throw new IllegalArgumentException("No corresponding enum value for integer"); + } + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java new file mode 100644 index 000000000000..78075511d4ad --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java @@ -0,0 +1,179 @@ +/** + * 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.alerts; + +import java.util.Date; +import javax.persistence.Cacheable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.dspace.core.ReloadableEntity; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * Database object representing system-wide alerts + */ +@Entity +@Cacheable +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy") +@Table(name = "systemwidealert") +public class SystemWideAlert implements ReloadableEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "alert_id_seq") + @SequenceGenerator(name = "alert_id_seq", sequenceName = "alert_id_seq", allocationSize = 1) + @Column(name = "alert_id", unique = true, nullable = false) + private Integer alertId; + + @Column(name = "message", nullable = false) + private String message; + + @Column(name = "allow_sessions") + private int allowSessions; + + @Column(name = "countdown_to") + @Temporal(TemporalType.TIMESTAMP) + private Date countdownTo; + + @Column(name = "active") + private boolean active; + + protected SystemWideAlert() { + } + + /** + * This method returns the ID that the system-wide alert holds within the database + * + * @return The ID that the system-wide alert holds within the database + */ + @Override + public Integer getID() { + return alertId; + } + + /** + * Set the ID for the system-wide alert + * + * @param alertID The ID to set + */ + public void setID(final Integer alertID) { + this.alertId = alertID; + } + + /** + * Retrieve the message of the system-wide alert + * + * @return the message of the system-wide alert + */ + public String getMessage() { + return message; + } + + /** + * Set the message of the system-wide alert + * + * @param message The message to set + */ + public void setMessage(final String message) { + this.message = message; + } + + /** + * Retrieve what kind of sessions are allowed while the system-wide alert is active + * + * @return what kind of sessions are allowed while the system-wide alert is active + */ + public int getAllowSessions() { + return allowSessions; + } + + /** + * Set what kind of sessions are allowed while the system-wide alert is active + * + * @param allowSessions Integer representing what kind of sessions are allowed + */ + public void setAllowSessions(final int allowSessions) { + this.allowSessions = allowSessions; + } + + /** + * Retrieve the date to which will be count down when the system-wide alert is active + * + * @return the date to which will be count down when the system-wide alert is active + */ + public Date getCountdownTo() { + return countdownTo; + } + + /** + * Set the date to which will be count down when the system-wide alert is active + * + * @param countdownTo The date to which will be count down + */ + public void setCountdownTo(final Date countdownTo) { + this.countdownTo = countdownTo; + } + + /** + * Retrieve whether the system-wide alert is active + * + * @return whether the system-wide alert is active + */ + public boolean isActive() { + return active; + } + + /** + * Set whether the system-wide alert is active + * + * @param active Whether the system-wide alert is active + */ + public void setActive(final boolean active) { + this.active = active; + } + + /** + * Return true if other is the same SystemWideAlert + * as this object, false otherwise + * + * @param other object to compare to + * @return true if object passed in represents the same + * system-wide alert as this object + */ + @Override + public boolean equals(Object other) { + return (other instanceof SystemWideAlert && + new EqualsBuilder().append(this.getID(), ((SystemWideAlert) other).getID()) + .append(this.getMessage(), ((SystemWideAlert) other).getMessage()) + .append(this.getAllowSessions(), ((SystemWideAlert) other).getAllowSessions()) + .append(this.getCountdownTo(), ((SystemWideAlert) other).getCountdownTo()) + .append(this.isActive(), ((SystemWideAlert) other).isActive()) + .isEquals()); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(this.getID()) + .append(this.getMessage()) + .append(this.getAllowSessions()) + .append(this.getCountdownTo()) + .append(this.isActive()) + .toHashCode(); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java new file mode 100644 index 000000000000..19efc2364d8e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java @@ -0,0 +1,110 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.alerts; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.dspace.alerts.dao.SystemWideAlertDAO; +import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.core.LogHelper; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The implementation for the {@link SystemWideAlertService} class + */ +public class SystemWideAlertServiceImpl implements SystemWideAlertService { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SystemWideAlertService.class); + + + @Autowired + private SystemWideAlertDAO systemWideAlertDAO; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private EPersonService ePersonService; + + public SystemWideAlert create(final Context context, final String message, + final AllowSessionsEnum allowSessionsType, + final Date countdownTo, final boolean active) throws SQLException { + SystemWideAlert systemWideAlert = new SystemWideAlert(); + systemWideAlert.setMessage(message); + systemWideAlert.setAllowSessions(allowSessionsType.getValue()); + systemWideAlert.setCountdownTo(countdownTo); + systemWideAlert.setActive(active); + + SystemWideAlert createdAlert = systemWideAlertDAO.create(context, systemWideAlert); + log.info(LogHelper.getHeader(context, "system_wide_alert_create", + "System Wide Alert has been created with message: '" + message + "' and ID " + + createdAlert.getID() + " and allowSessionsType " + allowSessionsType + + " and active set to " + active)); + + + return createdAlert; + } + + public SystemWideAlert find(final Context context, final int alertId) throws SQLException { + return systemWideAlertDAO.findByID(context, SystemWideAlert.class, alertId); + } + + public List findAll(final Context context) throws SQLException { + return systemWideAlertDAO.findAll(context, SystemWideAlert.class); + } + + public List findAll(final Context context, final int limit, final int offset) throws SQLException { + return systemWideAlertDAO.findAll(context, limit, offset); + } + + public List findAllActive(final Context context, final int limit, final int offset) + throws SQLException { + return systemWideAlertDAO.findAllActive(context, limit, offset); + } + + public void delete(final Context context, final SystemWideAlert systemWideAlert) + throws SQLException, IOException, AuthorizeException { + systemWideAlertDAO.delete(context, systemWideAlert); + log.info(LogHelper.getHeader(context, "system_wide_alert_create", + "System Wide Alert with ID " + systemWideAlert.getID() + " has been deleted")); + + } + + public void update(final Context context, final SystemWideAlert systemWideAlert) throws SQLException { + systemWideAlertDAO.save(context, systemWideAlert); + + } + + public boolean canNonAdminUserLogin(Context context) throws SQLException { + List active = findAllActive(context, 1, 0); + if (active == null || active.isEmpty()) { + return true; + } + return active.get(0).getAllowSessions() == AllowSessionsEnum.ALL.getValue(); + } + + public boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException { + if (authorizeService.isAdmin(context, ePerson)) { + return true; + } + List active = findAllActive(context, 1, 0); + if (active == null || active.isEmpty()) { + return true; + } + return active.get(0).getAllowSessions() != AllowSessionsEnum.ADMIN.getValue(); + } +} diff --git a/dspace-api/src/main/java/org/dspace/alerts/dao/SystemWideAlertDAO.java b/dspace-api/src/main/java/org/dspace/alerts/dao/SystemWideAlertDAO.java new file mode 100644 index 000000000000..b26b64758355 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/dao/SystemWideAlertDAO.java @@ -0,0 +1,45 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.alerts.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.alerts.SystemWideAlert; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; + +/** + * This is the Data Access Object for the {@link SystemWideAlert} object + */ +public interface SystemWideAlertDAO extends GenericDAO { + + /** + * Returns a list of all SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @param limit The limit for the amount of SystemWideAlerts returned + * @param offset The offset for the Processes to be returned + * @return The list of all SystemWideAlert objects in the Database + * @throws SQLException If something goes wrong + */ + List findAll(Context context, int limit, int offset) throws SQLException; + + /** + * Returns a list of all active SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @param limit The limit for the amount of SystemWideAlerts returned + * @param offset The offset for the Processes to be returned + * @return The list of all SystemWideAlert objects in the Database + * @throws SQLException If something goes wrong + */ + List findAllActive(Context context, int limit, int offset) throws SQLException; + + +} diff --git a/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java b/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java new file mode 100644 index 000000000000..13a0e0af236a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java @@ -0,0 +1,48 @@ +/** + * 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.alerts.dao.impl; + +import java.sql.SQLException; +import java.util.List; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.dspace.alerts.SystemWideAlert; +import org.dspace.alerts.SystemWideAlert_; +import org.dspace.alerts.dao.SystemWideAlertDAO; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; + +/** + * Implementation class for the {@link SystemWideAlertDAO} + */ +public class SystemWideAlertDAOImpl extends AbstractHibernateDAO implements SystemWideAlertDAO { + + public List findAll(final Context context, final int limit, final int offset) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SystemWideAlert.class); + Root alertRoot = criteriaQuery.from(SystemWideAlert.class); + criteriaQuery.select(alertRoot); + + return list(context, criteriaQuery, false, SystemWideAlert.class, limit, offset); + } + + public List findAllActive(final Context context, final int limit, final int offset) + throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, SystemWideAlert.class); + Root alertRoot = criteriaQuery.from(SystemWideAlert.class); + criteriaQuery.select(alertRoot); + criteriaQuery.where(criteriaBuilder.equal(alertRoot.get(SystemWideAlert_.active), true)); + + return list(context, criteriaQuery, false, SystemWideAlert.class, limit, offset); + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java b/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java new file mode 100644 index 000000000000..85b6ad304c95 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java @@ -0,0 +1,118 @@ +/** + * 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.alerts.service; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import org.dspace.alerts.AllowSessionsEnum; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * An interface for the ProcessService with methods regarding the SystemWideAlert workload + */ +public interface SystemWideAlertService { + + /** + * This method will create a SystemWideAlert object in the database + * + * @param context The relevant DSpace context + * @param message The message of the system-wide alert + * @param allowSessionsType Which sessions need to be allowed for the system-wide alert + * @param countdownTo The date to which to count down to when the system-wide alert is active + * @param active Whether the system-wide alert os active + * @return The created SystemWideAlert object + * @throws SQLException If something goes wrong + */ + SystemWideAlert create(Context context, String message, AllowSessionsEnum allowSessionsType, + Date countdownTo, boolean active + ) throws SQLException; + + /** + * This method will retrieve a SystemWideAlert object from the Database with the given ID + * + * @param context The relevant DSpace context + * @param alertId The alert id on which we'll search for in the database + * @return The system-wide alert that holds the given alert id + * @throws SQLException If something goes wrong + */ + SystemWideAlert find(Context context, int alertId) throws SQLException; + + /** + * Returns a list of all SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @return The list of all SystemWideAlert objects in the Database + * @throws SQLException If something goes wrong + */ + List findAll(Context context) throws SQLException; + + /** + * Returns a list of all SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @param limit The limit for the amount of system-wide alerts returned + * @param offset The offset for the system-wide alerts to be returned + * @return The list of all SystemWideAlert objects in the Database + * @throws SQLException If something goes wrong + */ + List findAll(Context context, int limit, int offset) throws SQLException; + + + /** + * Returns a list of all active SystemWideAlert objects in the database + * + * @param context The relevant DSpace context + * @return The list of all active SystemWideAlert objects in the database + * @throws SQLException If something goes wrong + */ + List findAllActive(Context context, int limit, int offset) throws SQLException; + + /** + * This method will delete the given SystemWideAlert object from the database + * + * @param context The relevant DSpace context + * @param systemWideAlert The SystemWideAlert object to be deleted + * @throws SQLException If something goes wrong + */ + void delete(Context context, SystemWideAlert systemWideAlert) + throws SQLException, IOException, AuthorizeException; + + + /** + * This method will be used to update the given SystemWideAlert object in the database + * + * @param context The relevant DSpace context + * @param systemWideAlert The SystemWideAlert object to be updated + * @throws SQLException If something goes wrong + */ + void update(Context context, SystemWideAlert systemWideAlert) throws SQLException; + + + /** + * Verifies if the user connected to the current context can retain its session + * + * @param context The relevant DSpace context + * @return if the user connected to the current context can retain its session + */ + boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException; + + + /** + * Verifies if a non admin user can log in + * + * @param context The relevant DSpace context + * @return if a non admin user can log in + */ + boolean canNonAdminUserLogin(Context context) throws SQLException; +} diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.4_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.4_2022.12.15__system_wide_alerts.sql new file mode 100644 index 000000000000..148ad5cc3ca0 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.4_2022.12.15__system_wide_alerts.sql @@ -0,0 +1,22 @@ +-- +-- 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/ +-- + +----------------------------------------------------------------------------------- +-- Create table for System wide alerts +----------------------------------------------------------------------------------- + +CREATE SEQUENCE alert_id_seq; + +CREATE TABLE systemwidealert +( + alert_id INTEGER NOT NULL PRIMARY KEY, + message VARCHAR(512), + allow_sessions INTEGER, + countdown_to TIMESTAMP, + active BOOLEAN +); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.4_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.4_2022.12.15__system_wide_alerts.sql new file mode 100644 index 000000000000..148ad5cc3ca0 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.4_2022.12.15__system_wide_alerts.sql @@ -0,0 +1,22 @@ +-- +-- 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/ +-- + +----------------------------------------------------------------------------------- +-- Create table for System wide alerts +----------------------------------------------------------------------------------- + +CREATE SEQUENCE alert_id_seq; + +CREATE TABLE systemwidealert +( + alert_id INTEGER NOT NULL PRIMARY KEY, + message VARCHAR(512), + allow_sessions INTEGER, + countdown_to TIMESTAMP, + active BOOLEAN +); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.4_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.4_2022.12.15__system_wide_alerts.sql new file mode 100644 index 000000000000..148ad5cc3ca0 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.4_2022.12.15__system_wide_alerts.sql @@ -0,0 +1,22 @@ +-- +-- 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/ +-- + +----------------------------------------------------------------------------------- +-- Create table for System wide alerts +----------------------------------------------------------------------------------- + +CREATE SEQUENCE alert_id_seq; + +CREATE TABLE systemwidealert +( + alert_id INTEGER NOT NULL PRIMARY KEY, + message VARCHAR(512), + allow_sessions INTEGER, + countdown_to TIMESTAMP, + active BOOLEAN +); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java new file mode 100644 index 000000000000..6f02e65008c0 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java @@ -0,0 +1,39 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import org.dspace.alerts.SystemWideAlert; +import org.dspace.app.rest.model.SystemWideAlertRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + +/** + * This converter will convert an object of {@Link SystemWideAlert} to an object of {@link SystemWideAlertRest} + */ +@Component +public class SystemWideAlertConverter implements DSpaceConverter { + + + @Override + public SystemWideAlertRest convert(SystemWideAlert systemWideAlert, Projection projection) { + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setProjection(projection); + systemWideAlertRest.setId(systemWideAlert.getID()); + systemWideAlertRest.setAlertId(systemWideAlert.getID()); + systemWideAlertRest.setMessage(systemWideAlert.getMessage()); + systemWideAlertRest.setAllowSessions(systemWideAlert.getAllowSessions()); + systemWideAlertRest.setCountdownTo(systemWideAlert.getCountdownTo()); + systemWideAlertRest.setActive(systemWideAlert.isActive()); + return systemWideAlertRest; + } + + @Override + public Class getModelClass() { + return SystemWideAlert.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java new file mode 100644 index 000000000000..e82dc0b7c8b5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java @@ -0,0 +1,88 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.app.rest.RestResourceController; + +/** + * This class serves as a REST representation for the {@link SystemWideAlert} class + */ +public class SystemWideAlertRest extends BaseObjectRest { + public static final String NAME = "systemwidealert"; + public static final String CATEGORY = RestAddressableModel.SYSTEM; + + public String getCategory() { + return CATEGORY; + } + + public Class getController() { + return RestResourceController.class; + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + private Integer alertId; + private String message; + private Integer allowSessions; + private Date countdownTo; + private boolean active; + + public Integer getAlertId() { + return alertId; + } + + public void setAlertId(final Integer alertID) { + this.alertId = alertID; + } + + public String getMessage() { + return message; + } + + public void setMessage(final String message) { + this.message = message; + } + + public Integer getAllowSessions() { + return allowSessions; + } + + public void setAllowSessions(final Integer allowSessions) { + this.allowSessions = allowSessions; + } + + public Date getCountdownTo() { + return countdownTo; + } + + public void setCountdownTo(final Date countdownTo) { + this.countdownTo = countdownTo; + } + + public boolean isActive() { + return active; + } + + public void setActive(final boolean active) { + this.active = active; + } + + @JsonIgnore + @Override + public Integer getId() { + return id; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SystemWideAlertResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SystemWideAlertResource.java new file mode 100644 index 000000000000..9089103d2bdb --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SystemWideAlertResource.java @@ -0,0 +1,23 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.alerts.SystemWideAlert; +import org.dspace.app.rest.model.SystemWideAlertRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * The Resource representation of a {@link SystemWideAlert} object + */ +@RelNameDSpaceResource(SystemWideAlertRest.NAME) +public class SystemWideAlertResource extends DSpaceResource { + public SystemWideAlertResource(SystemWideAlertRest content, Utils utils) { + super(content, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java new file mode 100644 index 000000000000..7fb451e7fdbc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java @@ -0,0 +1,164 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.alerts.AllowSessionsEnum; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.SystemWideAlertRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * The repository for the SystemWideAlert workload + */ +@Component(SystemWideAlertRest.CATEGORY + "." + SystemWideAlertRest.NAME) +public class SystemWideAlertRestRepository extends DSpaceRestRepository { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private SystemWideAlertService systemWideAlertService; + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected SystemWideAlertRest createAndReturn(Context context) throws SQLException, AuthorizeException { + SystemWideAlert systemWideAlert = createSystemWideAlert(context); + return converter.toRest(systemWideAlert, utils.obtainProjection()); + } + + + @Override + @PreAuthorize("permitAll()") + public SystemWideAlertRest findOne(Context context, Integer id) { + try { + SystemWideAlert systemWideAlert = systemWideAlertService.find(context, id); + if (systemWideAlert == null) { + throw new ResourceNotFoundException( + "systemWideAlert with id " + systemWideAlert.getID() + " was not found"); + } + return converter.toRest(systemWideAlert, utils.obtainProjection()); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + @Override + @PreAuthorize("permitAll()") + public Page findAll(Context context, Pageable pageable) { + try { + List systemWideAlerts = systemWideAlertService.findAll(context, pageable.getPageSize(), + Math.toIntExact( + pageable.getOffset())); + return converter.toRestPage(systemWideAlerts, pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected SystemWideAlertRest put(Context context, HttpServletRequest request, String apiCategory, String model, + Integer id, JsonNode jsonNode) throws SQLException, AuthorizeException { + + SystemWideAlertRest systemWideAlertRest; + try { + systemWideAlertRest = new ObjectMapper().readValue(jsonNode.toString(), SystemWideAlertRest.class); + } catch (JsonProcessingException e) { + throw new UnprocessableEntityException("Cannot parse JSON in request body", e); + } + + if (systemWideAlertRest == null || isBlank(systemWideAlertRest.getMessage())) { + throw new UnprocessableEntityException("system alert message cannot be blank"); + } + + SystemWideAlert systemWideAlert = systemWideAlertService.find(context, id); + if (systemWideAlert == null) { + throw new ResourceNotFoundException("system wide alert with id: " + id + " not found"); + } + + systemWideAlert.setMessage(systemWideAlertRest.getMessage()); + systemWideAlert.setAllowSessions(systemWideAlertRest.getAllowSessions()); + systemWideAlert.setCountdownTo(systemWideAlertRest.getCountdownTo()); + systemWideAlert.setActive(systemWideAlertRest.isActive()); + + systemWideAlertService.update(context, systemWideAlert); + context.commit(); + + return converter.toRest(systemWideAlert, utils.obtainProjection()); + } + + + /** + * Helper method to create a system-wide alert and deny creation when one already exists + * @param context The database context + * @return the created system-wide alert + * @throws SQLException + */ + private SystemWideAlert createSystemWideAlert(Context context) + throws SQLException { + List all = systemWideAlertService.findAll(context); + if (!all.isEmpty()) { + throw new DSpaceBadRequestException("A system wide alert already exists, no new value can be created. " + + "Try updating the existing one."); + } + + + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + ObjectMapper mapper = new ObjectMapper(); + SystemWideAlertRest systemWideAlertRest; + try { + ServletInputStream input = req.getInputStream(); + systemWideAlertRest = mapper.readValue(input, SystemWideAlertRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body.", e1); + } + + SystemWideAlert systemWideAlert; + + try { + systemWideAlert = systemWideAlertService.create(context, systemWideAlertRest.getMessage(), + AllowSessionsEnum.fromInt( + systemWideAlertRest.getAllowSessions()), + systemWideAlertRest.getCountdownTo(), + systemWideAlertRest.isActive()); + systemWideAlertService.update(context, systemWideAlert); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return systemWideAlert; + } + + + @Override + public Class getDomainClass() { + return SystemWideAlertRest.class; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java new file mode 100644 index 000000000000..48a996ee08d6 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java @@ -0,0 +1,293 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static com.jayway.jsonpath.JsonPath.read; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; +import java.sql.SQLException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.atomic.AtomicReference; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.alerts.AllowSessionsEnum; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.app.rest.model.SystemWideAlertRest; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.AuthorizeException; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test class to test the operations in the SystemWideAlertRestRepository + */ +public class SystemWideAlertRestRepositoryIT extends AbstractControllerIntegrationTest { + + private static final Logger log = LogManager.getLogger(SystemWideAlertRestRepositoryIT.class); + + @Autowired + private SystemWideAlertService systemWideAlertService; + + @After + public void destroy() throws Exception { + context.turnOffAuthorisationSystem(); + systemWideAlertService.findAll(context).stream().forEach(systemWideAlert -> { + try { + systemWideAlertService.delete(context, systemWideAlert); + } catch (SQLException | IOException | AuthorizeException e) { + log.error(e); + } + }); + context.restoreAuthSystemState(); + + super.destroy(); + } + + + @Test + public void findAllTest() throws Exception { + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = systemWideAlertService.create(context, "Test alert 1", + AllowSessionsEnum.CURRENT, countdownDate, + true); + SystemWideAlert systemWideAlert2 = systemWideAlertService.create(context, "Test alert 2", + AllowSessionsEnum.ADMIN, null, + false); + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + getClient().perform(get("/api/system/systemwidealerts/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.systemwidealerts", containsInAnyOrder( + allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ), + allOf( + hasJsonPath("$.alertId", is(systemWideAlert2.getID())), + hasJsonPath("$.message", is(systemWideAlert2.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert2.getAllowSessions())), + hasJsonPath("$.countdownTo", is(systemWideAlert2.getCountdownTo())), + hasJsonPath("$.active", is(systemWideAlert2.isActive())) + ) + ))); + + + } + + @Test + public void findOneTest() throws Exception { + + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = systemWideAlertService.create(context, "Test alert 1", + AllowSessionsEnum.CURRENT, countdownDate, + true); + SystemWideAlert systemWideAlert2 = systemWideAlertService.create(context, "Test alert 2", + AllowSessionsEnum.ADMIN, null, + false); + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + getClient().perform(get("/api/system/systemwidealerts/" + systemWideAlert1.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ) + )); + + } + + @Test + public void createTest() throws Exception { + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setMessage("Alert test message"); + systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + String authToken = getAuthToken(admin.getEmail(), password); + + AtomicReference idRef = new AtomicReference<>(); + + + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + getClient(authToken).perform(post("/api/system/systemwidealerts/") + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId"), + hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlertRest.getAllowSessions())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlertRest.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlertRest.isActive())) + ) + )) + .andDo(result -> idRef + .set((read(result.getResponse().getContentAsString(), "$.alertId")))); + + getClient().perform(get("/api/system/systemwidealerts/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(idRef.get())), + hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlertRest.getAllowSessions())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlertRest.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlertRest.isActive())) + ) + )); + + } + + @Test + public void createForbiddenTest() throws Exception { + + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setMessage("Alert test message"); + systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + + getClient(authToken).perform(post("/api/system/systemwidealerts/") + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + } + + @Test + public void createUnAuthorizedTest() throws Exception { + + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setMessage("Alert test message"); + systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + getClient().perform(post("/api/system/systemwidealerts/") + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + } + + + @Test + public void createWhenAlreadyExistsTest() throws Exception { + + SystemWideAlert systemWideAlert = systemWideAlertService.create(context, "Test alert", + AllowSessionsEnum.ADMIN, null, + false); + + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setMessage("Alert test message"); + systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + String authToken = getAuthToken(admin.getEmail(), password); + + getClient(authToken).perform(post("/api/system/systemwidealerts/") + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + + } + + @Test + public void putTest() throws Exception { + + SystemWideAlert systemWideAlert = systemWideAlertService.create(context, "Alert test message", + AllowSessionsEnum.ADMIN, null, + false); + + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); + systemWideAlertRest.setAlertId(systemWideAlert.getID()); + systemWideAlertRest.setMessage("Updated alert test message"); + systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setActive(true); + + ObjectMapper mapper = new ObjectMapper(); + + String authToken = getAuthToken(admin.getEmail(), password); + + + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + getClient(authToken).perform(put("/api/system/systemwidealerts/" + systemWideAlert.getID()) + .content(mapper.writeValueAsBytes(systemWideAlertRest)) + .contentType(contentType)) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId"), + hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlertRest.getAllowSessions())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlertRest.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlertRest.isActive())) + ) + )); + + getClient().perform(get("/api/system/systemwidealerts/" + systemWideAlert.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(systemWideAlert.getID())), + hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlertRest.getAllowSessions())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlertRest.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlertRest.isActive())) + ) + )); + + + } + + +} + diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 085ed0bd6e2c..d04cb7515565 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -54,6 +54,7 @@ + diff --git a/dspace/config/spring/api/core-dao-services.xml b/dspace/config/spring/api/core-dao-services.xml index ae4b5e6e3b6b..fa19cb28d9af 100644 --- a/dspace/config/spring/api/core-dao-services.xml +++ b/dspace/config/spring/api/core-dao-services.xml @@ -34,6 +34,8 @@ + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index a124ec830f0c..1854d8eee346 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -60,6 +60,8 @@ + + From 1cabe8e87c3f938b133bc41bc11b4f3ab2eb344e Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 23 Dec 2022 12:00:44 +0100 Subject: [PATCH 153/394] 97425: Update names of sessions enum --- .../java/org/dspace/alerts/AllowSessionsEnum.java | 14 +++++++------- .../dspace/alerts/SystemWideAlertServiceImpl.java | 4 ++-- .../app/rest/SystemWideAlertRestRepositoryIT.java | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java index 313923bcde2d..0c8b3f72d283 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java +++ b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java @@ -11,9 +11,9 @@ * Enum representing the options for allowing sessions */ public enum AllowSessionsEnum { - ALL(0), - CURRENT(1), - ADMIN(2); + ALLOW_ALL_SESSIONS(0), + ALLOW_CURRENT_SESSIONS_ONLY(1), + ALLOW_ADMIN_SESSIONS_ONLY(2); private int allowSessionsType; @@ -27,16 +27,16 @@ public int getValue() { public static AllowSessionsEnum fromInt(Integer alertAllowSessionType) { if (alertAllowSessionType == null) { - return AllowSessionsEnum.ALL; + return AllowSessionsEnum.ALLOW_ALL_SESSIONS; } switch (alertAllowSessionType) { case 0: - return AllowSessionsEnum.ALL; + return AllowSessionsEnum.ALLOW_ALL_SESSIONS; case 1: - return AllowSessionsEnum.CURRENT; + return AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY; case 2: - return AllowSessionsEnum.ADMIN; + return AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY; default: throw new IllegalArgumentException("No corresponding enum value for integer"); } diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java index 19efc2364d8e..73a130960e9c 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java @@ -94,7 +94,7 @@ public boolean canNonAdminUserLogin(Context context) throws SQLException { if (active == null || active.isEmpty()) { return true; } - return active.get(0).getAllowSessions() == AllowSessionsEnum.ALL.getValue(); + return active.get(0).getAllowSessions() == AllowSessionsEnum.ALLOW_ALL_SESSIONS.getValue(); } public boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException { @@ -105,6 +105,6 @@ public boolean canUserMaintainSession(Context context, EPerson ePerson) throws S if (active == null || active.isEmpty()) { return true; } - return active.get(0).getAllowSessions() != AllowSessionsEnum.ADMIN.getValue(); + return active.get(0).getAllowSessions() != AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY.getValue(); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java index 48a996ee08d6..db2c82a21c4e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java @@ -69,10 +69,10 @@ public void destroy() throws Exception { public void findAllTest() throws Exception { Date countdownDate = new Date(); SystemWideAlert systemWideAlert1 = systemWideAlertService.create(context, "Test alert 1", - AllowSessionsEnum.CURRENT, countdownDate, + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY, countdownDate, true); SystemWideAlert systemWideAlert2 = systemWideAlertService.create(context, "Test alert 2", - AllowSessionsEnum.ADMIN, null, + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY, null, false); DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); getClient().perform(get("/api/system/systemwidealerts/")) @@ -103,10 +103,10 @@ public void findOneTest() throws Exception { Date countdownDate = new Date(); SystemWideAlert systemWideAlert1 = systemWideAlertService.create(context, "Test alert 1", - AllowSessionsEnum.CURRENT, countdownDate, + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY, countdownDate, true); SystemWideAlert systemWideAlert2 = systemWideAlertService.create(context, "Test alert 2", - AllowSessionsEnum.ADMIN, null, + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY, null, false); DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); getClient().perform(get("/api/system/systemwidealerts/" + systemWideAlert1.getID())) @@ -216,7 +216,7 @@ public void createUnAuthorizedTest() throws Exception { public void createWhenAlreadyExistsTest() throws Exception { SystemWideAlert systemWideAlert = systemWideAlertService.create(context, "Test alert", - AllowSessionsEnum.ADMIN, null, + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY, null, false); SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); @@ -240,7 +240,7 @@ public void createWhenAlreadyExistsTest() throws Exception { public void putTest() throws Exception { SystemWideAlert systemWideAlert = systemWideAlertService.create(context, "Alert test message", - AllowSessionsEnum.ADMIN, null, + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY, null, false); SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); From 5330c2dc0f04dc2712ee08365262795ffa5e9f86 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Fri, 23 Dec 2022 15:29:13 +0200 Subject: [PATCH 154/394] [CST-7754] removed unused fields. --- .../SolrServiceSupervisionOrderIndexingPlugin.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java index 2ed19954e6da..99204885f8c9 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java @@ -14,14 +14,12 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.solr.common.SolrInputDocument; import org.dspace.content.Item; -import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; import org.dspace.discovery.indexobject.IndexableInProgressSubmission; import org.dspace.discovery.indexobject.IndexableWorkflowItem; import org.dspace.discovery.indexobject.IndexableWorkspaceItem; import org.dspace.supervision.SupervisionOrder; import org.dspace.supervision.service.SupervisionOrderService; -import org.dspace.workflow.WorkflowItemService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -31,12 +29,6 @@ */ public class SolrServiceSupervisionOrderIndexingPlugin implements SolrServiceIndexPlugin { - @Autowired(required = true) - private WorkspaceItemService workspaceItemService; - - @Autowired(required = true) - private WorkflowItemService workflowItemService; - @Autowired(required = true) private SupervisionOrderService supervisionOrderService; From 25ef93ac7b028ab40164dc04164317ab54e6404a Mon Sep 17 00:00:00 2001 From: jensroets Date: Fri, 23 Dec 2022 15:18:02 +0100 Subject: [PATCH 155/394] 97414 Advanced workflow actions: javadocs + moved isAdvanced method --- .../xmlworkflow/state/actions/Action.java | 12 +++++++++ .../state/actions/ActionAdvancedInfo.java | 26 ++++++++++++++++++- .../processingaction/ProcessingAction.java | 5 ++++ .../processingaction/RatingReviewAction.java | 18 +++++++++---- .../RatingReviewActionAdvancedInfo.java | 24 +++++++++++++++++ .../SelectReviewerAction.java | 5 ---- .../SelectReviewerActionAdvancedInfo.java | 14 ++++++++++ ...tingReviewActionAdvancedInfoConverter.java | 4 +++ ...ctReviewerActionAdvancedInfoConverter.java | 4 +++ .../RatingReviewActionAdvancedInfoRest.java | 19 ++++++++------ .../SelectReviewerActionAdvancedInfoRest.java | 3 +++ 11 files changed, 115 insertions(+), 19 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java index b09d6193618c..a76f16c8f017 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/Action.java @@ -195,14 +195,26 @@ protected void addErrorField(HttpServletRequest request, String fieldName) { setErrorFields(request, errorFields); } + /** + * Returns a list of advanced options that the user can select at this action + * @return A list of advanced options of this action, resulting in the next step of the workflow + */ protected List getAdvancedOptions() { return advancedOptions; } + /** + * Returns true if this Action has advanced options, false if it doesn't + * @return true if there are advanced options, false otherwise + */ protected boolean isAdvanced() { return !getAdvancedOptions().isEmpty(); } + /** + * Returns a list of advanced info required by the advanced options + * @return A list of advanced info required by the advanced options + */ protected List getAdvancedInfo() { return advancedInfo; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java index c6e68e1f5734..5a6451807e20 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java @@ -8,9 +8,33 @@ package org.dspace.xmlworkflow.state.actions; public interface ActionAdvancedInfo { + /** + * Getter for the type of Action + * + * @return String of format "action_info_" + Action type + */ String getType(); + + /** + * Setter for the Action type to be set in format "action_info_" + type + * + * @param type The type to be set + */ void setType(String type); + + /** + * Getter for the id of Action + * + * @return MD5 hash String of this Action + */ String getId(); - void setId(String id); + + /** + * Setter for the Action id to be set. + * This is an MD5 hash of the type and the stringified properties of the advanced info + * + * @param type The type of this Action to be included in the MD5 hash + */ + void setId(String type); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java index 8b8358a8d632..90bd044a3d3e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java @@ -48,4 +48,9 @@ public boolean isAuthorized(Context context, HttpServletRequest request, XmlWork task.getStepID().equals(getParent().getStep().getId()) && task.getActionID().equals(getParent().getId()); } + + @Override + protected boolean isAdvanced() { + return !getAdvancedOptions().isEmpty(); + } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java index 5d31054c9b98..5007b661fe72 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java @@ -23,6 +23,11 @@ import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +/** + * This action will allow users to rate a certain item + * if the score is lower than the maximum score the + * item will be sent to the next action/step else it will be rejected + */ public class RatingReviewAction extends ProcessingAction { private static final String RATING = "rating"; @@ -63,11 +68,6 @@ protected List getAdvancedOptions() { return Arrays.asList(RATING); } - @Override - protected boolean isAdvanced() { - return !getAdvancedOptions().isEmpty(); - } - @Override protected List getAdvancedInfo() { List advancedInfo = super.getAdvancedInfo(); @@ -80,10 +80,18 @@ protected List getAdvancedInfo() { return advancedInfo; } + /** + * Setter that sets the descriptionRequired property from workflow-actions.xml + * @param descriptionRequired boolean whether a description is required + */ public void setDescriptionRequired(boolean descriptionRequired) { this.descriptionRequired = descriptionRequired; } + /** + * Setter that sets the maxValue property from workflow-actions.xml + * @param maxValue integer of the maximum allowed value + */ public void setMaxValue(int maxValue) { this.maxValue = maxValue; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java index 26f1ddff825e..ffaaf3d54412 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java @@ -10,6 +10,10 @@ import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; import org.springframework.util.DigestUtils; +/** + * Class that holds the advanced information needed for the + * {@link org.dspace.xmlworkflow.state.actions.processingaction.RatingReviewAction} + */ public class RatingReviewActionAdvancedInfo implements ActionAdvancedInfo { private boolean descriptionRequired; private int maxValue; @@ -17,18 +21,38 @@ public class RatingReviewActionAdvancedInfo implements ActionAdvancedInfo { private String id; + /** + * Generic getter for the descriptionRequired boolean + * + * @return the descriptionRequired boolean value + */ public boolean isDescriptionRequired() { return descriptionRequired; } + /** + * Generic setter for the descriptionRequired boolean + * + * @param descriptionRequired The descriptionRequired boolean to be set + */ public void setDescriptionRequired(boolean descriptionRequired) { this.descriptionRequired = descriptionRequired; } + /** + * Generic getter for the maxValue + * + * @return the maxValue value + */ public int getMaxValue() { return maxValue; } + /** + * Generic setter for the maxValue + * + * @param maxValue The maxValue to be set + */ public void setMaxValue(int maxValue) { this.maxValue = maxValue; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java index 43fb00ae0468..9507245ab795 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java @@ -117,11 +117,6 @@ protected List getAdvancedOptions() { return Arrays.asList(SUBMIT_SELECT_REVIEWER); } - @Override - protected boolean isAdvanced() { - return !getAdvancedOptions().isEmpty(); - } - @Override protected List getAdvancedInfo() { List advancedInfo = super.getAdvancedInfo(); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java index c9fc4ebc82a2..8f88e9e1fd64 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java @@ -11,15 +11,29 @@ import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; import org.springframework.util.DigestUtils; +/** + * Class that holds the advanced information needed for the + * {@link org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction} + */ public class SelectReviewerActionAdvancedInfo implements ActionAdvancedInfo { private Role role; private String type; private String id; + /** + * Generic getter for the role + * + * @return the role value + */ public Role getRole() { return role; } + /** + * Generic setter for the role + * + * @param role The role to be set + */ public void setRole(Role role) { this.role = role; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java index 243435a6e937..49edcc116d65 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java @@ -11,6 +11,10 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.xmlworkflow.state.actions.processingaction.RatingReviewActionAdvancedInfo; +/** + * This converter is responsible for transforming the model representation of a RatingReviewActionAdvancedInfo to + * the REST representation of a RatingReviewActionAdvancedInfo + */ public class RatingReviewActionAdvancedInfoConverter implements DSpaceConverter { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java index 50d5c180a0d6..43127e116205 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java @@ -11,6 +11,10 @@ import org.dspace.app.rest.projection.Projection; import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo; +/** + * This converter is responsible for transforming the model representation of a SelectReviewerActionAdvancedInfo to + * the REST representation of a SelectReviewerActionAdvancedInfo + */ public class SelectReviewerActionAdvancedInfoConverter implements DSpaceConverter { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java index 499fc35be67c..273fe805a3f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest.model; +/** + * The RatingReviewActionAdvancedInfo REST Resource + */ public class RatingReviewActionAdvancedInfoRest extends WorkflowActionRest { private boolean descriptionRequired; @@ -17,7 +20,7 @@ public class RatingReviewActionAdvancedInfoRest extends WorkflowActionRest { /** * Generic getter for the description required boolean * - * @return the description required boolean value of this ScoreReviewActionAdvancedInfoRest + * @return the description required boolean value of this RatingReviewActionAdvancedInfoRest */ public boolean isDescriptionRequired() { return descriptionRequired; @@ -26,7 +29,7 @@ public boolean isDescriptionRequired() { /** * Generic setter for the description required boolean * - * @param descriptionRequired The description required boolean to be set on this ScoreReviewActionAdvancedInfoRest + * @param descriptionRequired The description required boolean to be set on this RatingReviewActionAdvancedInfoRest */ public void setDescriptionRequired(boolean descriptionRequired) { this.descriptionRequired = descriptionRequired; @@ -35,7 +38,7 @@ public void setDescriptionRequired(boolean descriptionRequired) { /** * Generic getter for the max value * - * @return the max value of this ScoreReviewActionAdvancedInfoRest + * @return the max value of this RatingReviewActionAdvancedInfoRest */ public int getMaxValue() { return maxValue; @@ -44,7 +47,7 @@ public int getMaxValue() { /** * Generic setter for the max value * - * @param maxValue The max value to be set on this ScoreReviewActionAdvancedInfoRest + * @param maxValue The max value to be set on this RatingReviewActionAdvancedInfoRest */ public void setMaxValue(int maxValue) { this.maxValue = maxValue; @@ -53,7 +56,7 @@ public void setMaxValue(int maxValue) { /** * Generic getter for the type * - * @return the type of this ScoreReviewActionAdvancedInfoRest + * @return the type of this RatingReviewActionAdvancedInfoRest */ @Override public String getType() { @@ -63,7 +66,7 @@ public String getType() { /** * Generic setter for the type * - * @param type The type to be set on this ScoreReviewActionAdvancedInfoRest + * @param type The type to be set on this RatingReviewActionAdvancedInfoRest */ public void setType(String type) { this.type = type; @@ -72,7 +75,7 @@ public void setType(String type) { /** * Generic getter for the id * - * @return the id of this ScoreReviewActionAdvancedInfoRest + * @return the id of this RatingReviewActionAdvancedInfoRest */ @Override public String getId() { @@ -82,7 +85,7 @@ public String getId() { /** * Generic setter for the id * - * @param id The id to be set on this ScoreReviewActionAdvancedInfoRest + * @param id The id to be set on this RatingReviewActionAdvancedInfoRest */ @Override public void setId(String id) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java index 1fa209bb3bfa..d09fbfb6e6cb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java @@ -9,6 +9,9 @@ import org.dspace.xmlworkflow.Role; +/** + * The SelectReviewerActionAdvancedInfoRest REST Resource + */ public class SelectReviewerActionAdvancedInfoRest extends WorkflowActionRest { private Role role; From b54bdd17125737d83df730435fd17d9d698dc581 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Wed, 28 Dec 2022 10:09:54 +0100 Subject: [PATCH 156/394] [CST - 7754] refactoring and added support for NONE type --- .../enumeration/SupervisionOrderType.java | 13 ++++++++++++- .../repository/SupervisionOrderRestRepository.java | 10 +++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java index 698e9d234efb..a071d1695d5f 100644 --- a/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java +++ b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java @@ -7,11 +7,22 @@ */ package org.dspace.supervision.enumeration; + /** * * @author Mohamed Eskander (mohamed.eskander at 4science dot it) */ public enum SupervisionOrderType { OBSERVER, - EDITOR + NONE, + EDITOR; + + public static boolean invalid(String type) { + try { + SupervisionOrderType.valueOf(type); + return false; + } catch (IllegalArgumentException ignored) { + return true; + } + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java index 7d7ede818902..020fb4653541 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java @@ -10,9 +10,11 @@ import static org.dspace.authorize.ResourcePolicy.TYPE_SUBMISSION; import java.sql.SQLException; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; @@ -183,9 +185,11 @@ private void validateParameters(String itemId, String groupId, String type) { if (Objects.isNull(type)) { throw new MissingParameterException("Missing type parameter"); - } else if (!type.equals(SupervisionOrderType.EDITOR.toString()) && - !type.equals(SupervisionOrderType.OBSERVER.toString())) { - throw new IllegalArgumentException("wrong type value, Type must be (EDITOR or OBSERVER)"); + } else if (SupervisionOrderType.invalid(type)) { + throw new IllegalArgumentException("wrong type value, Type must be (" + + Arrays.stream(SupervisionOrderType.values()) + .map(Enum::name) + .collect(Collectors.joining(" or ")) + ")"); } } From 3dc98bae78278f4d7d552bff37c32ec04fba1908 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 29 Dec 2022 15:17:54 +0100 Subject: [PATCH 157/394] 97298: #3281 Self-register - type param --- .../RegistrationRestRepository.java | 26 ++++++++++++------- .../rest/RegistrationRestRepositoryIT.java | 13 ++++++++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java index 74a9c91f7e3a..177c75093faa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -48,6 +48,10 @@ public class RegistrationRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { public RegistrationRest createAndReturn(Context context) { HttpServletRequest request = requestService.getCurrentRequest().getHttpServletRequest(); ObjectMapper mapper = new ObjectMapper(); - String type = request.getParameter("type"); RegistrationRest registrationRest; try { ServletInputStream input = request.getInputStream(); @@ -88,8 +91,13 @@ public RegistrationRest createAndReturn(Context context) { if (StringUtils.isBlank(registrationRest.getEmail())) { throw new UnprocessableEntityException("The email cannot be omitted from the Registration endpoint"); } + String type = request.getParameter(TYPE_QUERY_PARAM); + if (!type.equalsIgnoreCase(TYPE_FORGOT) && !type.equalsIgnoreCase(TYPE_REGISTER)) { + throw new IllegalArgumentException(String.format("Needs query param '%s' with value %s or %s indicating " + + "what kind of registration request it is", TYPE_QUERY_PARAM, TYPE_FORGOT, TYPE_REGISTER)); + } try { - if (type != null && type.equals("register") && + if (type.equals(TYPE_REGISTER) && !authenticationService.canSelfRegister(context, request, registrationRest.getEmail())) { throw new DSpaceBadRequestException("registration is not allowed with this email address"); } @@ -97,13 +105,13 @@ public RegistrationRest createAndReturn(Context context) { log.error("something went wrong while checking if registration is allowed"); throw new InternalServerErrorException(e); } - EPerson eperson = null; - try { - eperson = ePersonService.findByEmail(context, registrationRest.getEmail()); - } catch (SQLException e) { - log.error("Something went wrong retrieving EPerson for email: " + registrationRest.getEmail(), e); - } - if (eperson != null) { + if (type.equalsIgnoreCase(TYPE_FORGOT)) { + EPerson eperson = null; + try { + eperson = ePersonService.findByEmail(context, registrationRest.getEmail()); + } catch (SQLException e) { + log.error("Something went wrong retrieving EPerson for email: " + registrationRest.getEmail(), e); + } try { if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail())) { throw new DSpaceBadRequestException("Password cannot be updated for the given EPerson with email: " diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java index 61480bd06d23..00d11868d69c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RegistrationRestRepositoryIT.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_FORGOT; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_QUERY_PARAM; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_REGISTER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -102,6 +105,7 @@ private void createTokenForEmail(String email) throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(email); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -118,6 +122,7 @@ public void registrationFlowTest() throws Exception { try { getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -128,6 +133,7 @@ public void registrationFlowTest() throws Exception { String newEmail = "newEPersonTest@gmail.com"; registrationRest.setEmail(newEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -140,6 +146,7 @@ public void registrationFlowTest() throws Exception { newEmail = "newEPersonTestTwo@gmail.com"; registrationRest.setEmail(newEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().is(HttpServletResponse.SC_UNAUTHORIZED)); @@ -167,6 +174,7 @@ public void testRegisterDomainRegistered() throws Exception { ObjectMapper mapper = new ObjectMapper(); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); @@ -193,7 +201,7 @@ public void testRegisterDomainNotRegistred() throws Exception { ObjectMapper mapper = new ObjectMapper(); getClient().perform(post("/api/eperson/registrations") - .param("type", "register") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isBadRequest()); @@ -224,7 +232,7 @@ public void testRegisterDomainNotRegisteredMailAddressRegistred() throws Excepti ObjectMapper mapper = new ObjectMapper(); getClient().perform(post("/api/eperson/registrations") - .param("type", "register") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isBadRequest()); @@ -251,6 +259,7 @@ public void forgotPasswordTest() throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(eperson.getEmail()); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_FORGOT) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) .andExpect(status().isCreated()); From 6a594cb62b0921b6f6cd74796f6f56405ff68e51 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 29 Dec 2022 16:57:05 +0100 Subject: [PATCH 158/394] 97298: #3281 Self-register - fix, needs existent user for forgot --- .../RegistrationRestRepository.java | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java index 177c75093faa..d1d13ded7c5e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -12,7 +12,6 @@ import javax.mail.MessagingException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.InternalServerErrorException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; @@ -96,42 +95,38 @@ public RegistrationRest createAndReturn(Context context) { throw new IllegalArgumentException(String.format("Needs query param '%s' with value %s or %s indicating " + "what kind of registration request it is", TYPE_QUERY_PARAM, TYPE_FORGOT, TYPE_REGISTER)); } + EPerson eperson = null; try { - if (type.equals(TYPE_REGISTER) && - !authenticationService.canSelfRegister(context, request, registrationRest.getEmail())) { - throw new DSpaceBadRequestException("registration is not allowed with this email address"); - } + eperson = ePersonService.findByEmail(context, registrationRest.getEmail()); } catch (SQLException e) { - log.error("something went wrong while checking if registration is allowed"); - throw new InternalServerErrorException(e); + log.error("Something went wrong retrieving EPerson for email: " + registrationRest.getEmail(), e); } - if (type.equalsIgnoreCase(TYPE_FORGOT)) { - EPerson eperson = null; - try { - eperson = ePersonService.findByEmail(context, registrationRest.getEmail()); - } catch (SQLException e) { - log.error("Something went wrong retrieving EPerson for email: " + registrationRest.getEmail(), e); - } + if (eperson != null && type.equalsIgnoreCase(TYPE_FORGOT)) { try { if (!AuthorizeUtil.authorizeUpdatePassword(context, eperson.getEmail())) { throw new DSpaceBadRequestException("Password cannot be updated for the given EPerson with email: " - + eperson.getEmail()); + + eperson.getEmail()); } accountService.sendForgotPasswordInfo(context, registrationRest.getEmail()); } catch (SQLException | IOException | MessagingException | AuthorizeException e) { log.error("Something went wrong with sending forgot password info email: " - + registrationRest.getEmail(), e); + + registrationRest.getEmail(), e); } - } else { + } else if (type.equalsIgnoreCase(TYPE_REGISTER)) { try { + String email = registrationRest.getEmail(); if (!AuthorizeUtil.authorizeNewAccountRegistration(context, request)) { throw new AccessDeniedException( "Registration is disabled, you are not authorized to create a new Authorization"); } - accountService.sendRegistrationInfo(context, registrationRest.getEmail()); + if (!authenticationService.canSelfRegister(context, request, email)) { + throw new DSpaceBadRequestException(String.format("Registration is not allowed with email address" + + " %s", email)); + } + accountService.sendRegistrationInfo(context, email); } catch (SQLException | IOException | MessagingException | AuthorizeException e) { log.error("Something went wrong with sending registration info email: " - + registrationRest.getEmail(), e); + + registrationRest.getEmail(), e); } } return null; From 2f196624e0c4ea5f96989169e4e194827cf96123 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 29 Dec 2022 17:08:58 +0100 Subject: [PATCH 159/394] 97298: Self-register - test fixes --- .../dspace/app/rest/EPersonRestRepositoryIT.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index e6774ebb39d3..88996caf56a2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -11,6 +11,8 @@ import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_QUERY_PARAM; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_REGISTER; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; @@ -2179,6 +2181,7 @@ public void registerNewAccountPatchUpdatePasswordRandomUserUuidFail() throws Exc RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2229,6 +2232,7 @@ public void postEPersonWithTokenWithoutEmailProperty() throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2293,6 +2297,7 @@ public void postEPersonWithTokenWithEmailProperty() throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2355,6 +2360,7 @@ public void postEPersonWithTokenWithEmailAndSelfRegisteredProperty() throws Exce RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2421,6 +2427,7 @@ public void postEPersonWithTokenWithTwoTokensDifferentEmailProperty() throws Exc RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2430,6 +2437,7 @@ public void postEPersonWithTokenWithTwoTokensDifferentEmailProperty() throws Exc RegistrationRest registrationRestTwo = new RegistrationRest(); registrationRestTwo.setEmail(newRegisterEmailTwo); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRestTwo))) .andExpect(status().isCreated()); @@ -2480,6 +2488,7 @@ public void postEPersonWithRandomTokenWithEmailProperty() throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2528,6 +2537,7 @@ public void postEPersonWithTokenWithEmailAndSelfRegisteredFalseProperty() throws RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2577,6 +2587,7 @@ public void postEPersonWithTokenWithoutLastNameProperty() throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2644,6 +2655,7 @@ public void postEPersonWithTokenWithoutFirstNameProperty() throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2712,6 +2724,7 @@ public void postEPersonWithTokenWithoutPasswordProperty() throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2759,6 +2772,7 @@ public void postEPersonWithWrongToken() throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(eperson.getEmail()); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -2808,6 +2822,7 @@ public void postEPersonWithTokenWithEmailPropertyAnonUser() throws Exception { RegistrationRest registrationRest = new RegistrationRest(); registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); From f8037d465a313770931a984611d702a4290917b2 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 29 Dec 2022 17:30:48 +0100 Subject: [PATCH 160/394] 97298: Self-register - test fixes --- .../java/org/dspace/app/rest/EPersonRestRepositoryIT.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index ce9469639a04..8c7f80e5fb59 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -3152,6 +3152,7 @@ public void validatePasswordRobustnessContainingAtLeastAnUpperCaseCharUnprocessa registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -3197,6 +3198,7 @@ public void validatePasswordRobustnessContainingAtLeastAnUpperCaseCharTest() thr registrationRest.setEmail(newRegisterEmail); getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -3432,4 +3434,4 @@ private String buildPasswordAddOperationPatchBody(String password, String curren } -} \ No newline at end of file +} From d5888a8cfddef8fae927e968a116ba788f2522be Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 29 Dec 2022 18:36:15 +0100 Subject: [PATCH 161/394] [CST-7756] Rename type property to subscriptionType in the subscriptionType object --- .../rest/converter/SubscriptionConverter.java | 2 +- .../app/rest/model/SubscriptionRest.java | 8 ++-- .../rest/SubscriptionRestRepositoryIT.java | 44 +++++++++++++++---- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java index 5c7aeaef4273..fea7fe0e26fc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java @@ -45,7 +45,7 @@ public SubscriptionRest convert(Subscription subscription, Projection projection subscriptionParameterRestList.add(subscriptionParameterRest); } rest.setSubscriptionParameterList(subscriptionParameterRestList); - rest.setType(subscription.getType()); + rest.setSubscriptionType(subscription.getType()); return rest; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index 4900b6fd235d..6e23d850f564 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -27,7 +27,7 @@ public class SubscriptionRest extends BaseObjectRest { public static final String EPERSON = "ePerson"; private Integer id; - private String type; + private String subscriptionType; private List subscriptionParameterList = new ArrayList<>(); @Override @@ -45,8 +45,8 @@ public String getType() { return NAME; } - public void setType(String type) { - this.type = type; + public void setSubscriptionType(String type) { + this.subscriptionType = type; } public List getSubscriptionParameterList() { @@ -58,7 +58,7 @@ public void setSubscriptionParameterList(List subscri } public String getSubscriptionType() { - return this.type; + return this.subscriptionType; } @Override diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index f5975109478e..bfe837534860 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -386,7 +386,7 @@ public void createSubscriptionUnauthorizedTest() throws Exception { subscriptionParameterRestList.add(subscriptionParameterRest); SubscriptionRest subscriptionRest = new SubscriptionRest(); - subscriptionRest.setType("testType"); + subscriptionRest.setSubscriptionType("testType"); MultiValueMap params = new LinkedMultiValueMap(); params.add("dspace_object_id", publicItem.getID().toString()); @@ -409,7 +409,7 @@ public void createSubscriptionAdminForOtherPersonTest() throws Exception { context.turnOffAuthorisationSystem(); Map map = new HashMap<>(); - map.put("type", "testType"); + map.put("subscriptionType", "testType"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "Frequency"); @@ -445,7 +445,7 @@ public void createSubscriptionByEPersonTest() throws Exception { context.turnOffAuthorisationSystem(); Map map = new HashMap<>(); - map.put("type", "testType"); + map.put("subscriptionType", "testType"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "Frequency"); @@ -476,6 +476,34 @@ public void createSubscriptionByEPersonTest() throws Exception { } } + @Test + public void createInvalidSubscriptionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("type", "testType"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "Frequency"); + sub_list.put("value", "daily"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference(); + + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("dspace_object_id", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + + } + @Test public void createSubscriptionPersonForAnotherPersonTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -486,7 +514,7 @@ public void createSubscriptionPersonForAnotherPersonTest() throws Exception { .build(); Map map = new HashMap<>(); - map.put("type", "testType"); + map.put("subscriptionType", "testType"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "Frequency"); @@ -576,7 +604,7 @@ public void putSubscriptionUnauthorizedTest() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); + newSubscription.put("subscriptionType", "test"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "frequency"); @@ -606,7 +634,7 @@ public void putSubscriptionForbiddenTest() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); + newSubscription.put("subscriptionType", "test"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "Frequency"); @@ -637,7 +665,7 @@ public void putSubscriptionTest() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); + newSubscription.put("subscriptionType", "test"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "Frequency"); @@ -674,7 +702,7 @@ public void putSubscriptionAdminTest() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); - newSubscription.put("type", "test"); + newSubscription.put("subscriptionType", "test"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "Frequency"); From c52042aad5c985fa92bc32bfff06958673d0067e Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 30 Dec 2022 09:22:45 +0100 Subject: [PATCH 162/394] [CST-7756] Fix checkstyle --- .../dspace/eperson/service/SubscribeService.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 98b810ac4905..6e5e6c66b9aa 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -166,7 +166,7 @@ public List findSubscriptionsByEPersonAndDso(Context context, EPer * @throws SQLException An exception that provides information on a database access error or other errors. */ public Subscription updateSubscription(Context context, Integer id, EPerson eperson, DSpaceObject dSpaceObject, - List subscriptionParameterList, String type) throws SQLException; + List subscriptionParameterList, String type) throws SQLException; /** * Adds a parameter to a subscription @@ -176,8 +176,8 @@ public Subscription updateSubscription(Context context, Integer id, EPerson eper * @param subscriptionParameter SubscriptionParameter subscriptionParameter * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription addSubscriptionParameter(Context context,Integer id,SubscriptionParameter subscriptionParameter) - throws SQLException; + public Subscription addSubscriptionParameter(Context context,Integer id, + SubscriptionParameter subscriptionParameter) throws SQLException; /** * Deletes a parameter from subscription @@ -187,14 +187,14 @@ public Subscription addSubscriptionParameter(Context context,Integer id,Subscrip * @param subscriptionParam SubscriptionParameter subscriptionParameter * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription removeSubscriptionParameter(Context context, Integer id,SubscriptionParameter subscriptionParam) - throws SQLException; + public Subscription removeSubscriptionParameter(Context context, Integer id, + SubscriptionParameter subscriptionParam) throws SQLException; /** * Deletes a subscription * * @param context DSpace context - * @param id Integer id of subscription + * @param subscription The subscription to delete * @throws SQLException An exception that provides information on a database access error or other errors. */ public void deleteSubscription(Context context, Subscription subscription) throws SQLException; @@ -207,8 +207,8 @@ public Subscription removeSubscriptionParameter(Context context, Integer id,Subs * @param frequencyValue String frequency value of subscription * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List findAllSubscriptionsByTypeAndFrequency(Context context, String type,String frequencyValue) - throws SQLException; + public List findAllSubscriptionsByTypeAndFrequency(Context context, String type, + String frequencyValue) throws SQLException; /** * Counts all subscriptions From bca8d6320a1ce95e78b3ef46fdf19d0284ac51d5 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 30 Dec 2022 11:17:53 +0100 Subject: [PATCH 163/394] [CST-7756] fixed database scripts version number --- ...le_subscriptionparamter_change_columns_subscription_table.sql} | 0 ...le_subscriptionparamter_change_columns_subscription_table.sql} | 0 ...le_subscriptionparamter_change_columns_subscription_table.sql} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/{V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql => V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql} (100%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/{V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql => V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql} (100%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/{V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql => V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql} (100%) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2021.07.30__add_table_subscriptionparamter_change_columns_subscription_table.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql From 01634d141427c69b7c932b518371f442952df0eb Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 30 Dec 2022 15:55:23 +0100 Subject: [PATCH 164/394] [CST-7754] otherworkspace discovery configuration shows only supervised workspace items, when a supervision order is deleted grants given to supervision groups on an item are revoked. --- .../SupervisionOrderRestRepository.java | 16 ++++++++++++++++ .../rest/SupervisionOrderRestRepositoryIT.java | 11 +++++++++++ dspace/config/spring/api/discovery.xml | 4 +--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java index 020fb4653541..8cf73e8fe993 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java @@ -146,7 +146,9 @@ protected void delete(Context context, Integer id) " with id: " + id + " not found" ); } + removeGroupPoliciesToItem(context, supervisionOrder.getItem(), supervisionOrder.getGroup()); supervisionOrderService.delete(context, supervisionOrder); + } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } @@ -201,6 +203,7 @@ private void addGroupPoliciesToItem(Context context, Item item, Group group, Str if (type.equals("EDITOR")) { addGroupPolicyToItem(context, item, Constants.READ, group, TYPE_SUBMISSION); addGroupPolicyToItem(context, item, Constants.WRITE, group, TYPE_SUBMISSION); + addGroupPolicyToItem(context, item, Constants.ADD, group, TYPE_SUBMISSION); } else if (type.equals("OBSERVER")) { addGroupPolicyToItem(context, item, Constants.READ, group, TYPE_SUBMISSION); } @@ -220,4 +223,17 @@ private void addGroupPolicyToItem(Context context, Item item, int action, Group } } + private void removeGroupPoliciesToItem(Context context, Item item, Group group) + throws AuthorizeException, SQLException { + authorizeService.removeGroupPolicies(context, item, group); + List bundles = item.getBundles(); + for (Bundle bundle : bundles) { + authorizeService.removeGroupPolicies(context, bundle, group); + List bits = bundle.getBitstreams(); + for (Bitstream bitstream : bits) { + authorizeService.removeGroupPolicies(context, bitstream, group); + } + } + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java index 7bd2553c9b97..e6b29fd67e16 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -42,6 +42,7 @@ import org.dspace.supervision.SupervisionOrder; import org.dspace.supervision.service.SupervisionOrderService; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -927,6 +928,16 @@ public void deleteByAdminTest() throws Exception { getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) .andExpect(status().isNotFound()); + + String patchBody = getPatchContent(List.of(new ReplaceOperation("/withdrawn", true))); + + getClient(getAuthToken(eperson.getEmail(), password)).perform(patch("/api/core/items/" + item.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); + + Assert.assertTrue(item.getResourcePolicies().stream() + .noneMatch(rp -> group.getID().equals(rp.getGroup().getID()))); } } \ No newline at end of file diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index c3606c407d2f..586ed30e1ec8 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -912,7 +912,6 @@ -
@@ -921,7 +920,6 @@ -
@@ -941,7 +939,7 @@ - search.resourcetype:WorkspaceItem + search.resourcetype:WorkspaceItem AND supervised:true From 7a08fe50fec817677029191f12bbc865b656487d Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Mon, 2 Jan 2023 10:44:59 +0200 Subject: [PATCH 165/394] [CST-7754] test supervision order is deleted if workspaceItem or group is deleted. --- .../SupervisionOrderRestRepositoryIT.java | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java index e6b29fd67e16..2e3838abed19 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; import org.dspace.app.rest.model.patch.AddOperation; @@ -35,8 +36,10 @@ import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.SupervisionOrderBuilder; +import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.supervision.SupervisionOrder; @@ -940,4 +943,146 @@ public void deleteByAdminTest() throws Exception { .noneMatch(rp -> group.getID().equals(rp.getGroup().getID()))); } + @Test + public void deleteItemThenSupervisionOrderBeDeletedTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/core/items/" + item.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNotFound()); + + } + + @Test + public void deleteGroupThenSupervisionOrderBeDeletedTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Item item = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .build(); + + Group group = + GroupBuilder.createGroup(context) + .withName("group") + .addMember(eperson) + .build(); + + SupervisionOrder supervisionOrder = + SupervisionOrderBuilder.createSupervisionOrder(context, item, group) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/eperson/groups/" + group.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) + .andExpect(status().isNotFound()); + + } + + @Test + public void deleteWorkspaceItemThenSupervisionOrderBeDeletedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRef = new AtomicReference<>(); + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRef.get())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(delete("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRef.get())) + .andExpect(status().isNotFound()); + + } + } \ No newline at end of file From 7ee46e0cba582a299a3f0a74e99d251f7409742f Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Mon, 2 Jan 2023 09:58:31 +0100 Subject: [PATCH 166/394] 97183 Processing PR review feedback --- .../src/main/java/org/dspace/content/ItemServiceImpl.java | 2 ++ .../src/main/java/org/dspace/discovery/IndexingUtils.java | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 156c8b271237..380941b776d0 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1100,6 +1100,7 @@ private DiscoverResult retrieveItemsWithEdit(Context context, DiscoverQuery disc return searchService.search(context, discoverQuery); } + @Override public List findItemsWithEdit(Context context, int offset, int limit) throws SQLException, SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); @@ -1112,6 +1113,7 @@ public List findItemsWithEdit(Context context, int offset, int limit) .collect(Collectors.toList()); } + @Override public int countItemsWithEdit(Context context) throws SQLException, SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.setMaxResults(0); diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java index c4848c743292..8dd02f5d44e0 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexingUtils.java @@ -34,8 +34,6 @@ private IndexingUtils() { * Retrieve all ancestor communities of a given community, with the first one being the given community and the * last one being the root. *

- * TODO: can be done in a single SQL query with recursive common table expressions - * TODO: should probably be moved to CommunityService * * @param context DSpace context object * @param community Community for which we search the ancestors From 00a11c58cbc1ed6e1d63a7a412ccd2e6397ababc Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 2 Jan 2023 12:21:00 +0100 Subject: [PATCH 167/394] 97538: Unauthorized tests should now be anonymous --- .../org/dspace/app/rest/VocabularyEntryDetailsIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index fa277bf8a99e..73d6eed1e4dd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -70,7 +70,7 @@ public void findOneTest() throws Exception { } @Test - public void findOneUnauthorizedTest() throws Exception { + public void findOneAnonymousTest() throws Exception { String idAuthority = "srsc:SCB110"; getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority)) .andExpect(status().isOk()); @@ -254,7 +254,7 @@ public void searchTopBadRequestTest() throws Exception { } @Test - public void searchTopUnauthorizedTest() throws Exception { + public void searchTopAnonymousTest() throws Exception { getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()); @@ -328,7 +328,7 @@ public void srscSearchByParentWrongIdTest() throws Exception { } @Test - public void srscSearchTopUnauthorizedTest() throws Exception { + public void srscSearchTopAnonymousTest() throws Exception { getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top") .param("vocabulary", "srsc")) .andExpect(status().isOk()); @@ -353,7 +353,7 @@ public void findParentByChildBadRequestTest() throws Exception { } @Test - public void findParentByChildUnauthorizedTest() throws Exception { + public void findParentByChildAnonymousTest() throws Exception { getClient().perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent")) .andExpect(status().isOk()); } From bf084d74a1e7a6271240599b7e0ed800170894c8 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 2 Jan 2023 12:28:54 +0100 Subject: [PATCH 168/394] 97538: Remove readability change --- .../dspace/content/authority/DSpaceControlledVocabulary.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java index d5e350faf0ec..16632ee5466b 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/DSpaceControlledVocabulary.java @@ -132,8 +132,6 @@ protected void init() { String filename = vocabulariesPath + vocabularyName + ".xml"; log.info("Loading " + filename); vocabulary = new InputSource(filename); - - hierarchyDelimiter = " " + hierarchyDelimiter + " "; } } From de6bccafa3a30a29abb16d8cde5d848019c9c11e Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 2 Jan 2023 13:36:11 +0100 Subject: [PATCH 169/394] 97538: Remove readability change in IT --- .../app/rest/VocabularyEntryDetailsIT.java | 50 +++++++++---------- .../app/rest/VocabularyRestRepositoryIT.java | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java index 73d6eed1e4dd..dad6cd8b4605 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyEntryDetailsIT.java @@ -57,7 +57,7 @@ public void findOneTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$", VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB110", "Religion/Theology", - "HUMANITIES and RELIGION :: Religion/Theology"))) + "HUMANITIES and RELIGION::Religion/Theology"))) .andExpect(jsonPath("$.selectable", is(true))) .andExpect(jsonPath("$.otherInformation.id", is("SCB110"))) .andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi"))) @@ -170,11 +170,11 @@ public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception { .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", "Algebra, geometry and mathematical analysis", - "MATHEMATICS :: Algebra, geometry and mathematical analysis"), + "MATHEMATICS::Algebra, geometry and mathematical analysis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", - "MATHEMATICS :: Applied mathematics"), + "MATHEMATICS::Applied mathematics"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", - "MATHEMATICS :: Other mathematics") + "MATHEMATICS::Other mathematics") ))) .andExpect(jsonPath("$._embedded.children[*].otherInformation.parent", Matchers.everyItem(is("MATHEMATICS")))) @@ -271,9 +271,9 @@ public void srscSearchByParentFirstLevelPaginationTest() throws Exception { .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401", "Algebra, geometry and mathematical analysis", - "MATHEMATICS :: Algebra, geometry and mathematical analysis"), + "MATHEMATICS::Algebra, geometry and mathematical analysis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics", - "MATHEMATICS :: Applied mathematics") + "MATHEMATICS::Applied mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -286,7 +286,7 @@ public void srscSearchByParentFirstLevelPaginationTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.contains( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics", - "MATHEMATICS :: Other mathematics") + "MATHEMATICS::Other mathematics") ))) .andExpect(jsonPath("$.page.totalElements", is(3))) .andExpect(jsonPath("$.page.totalPages", is(2))) @@ -300,13 +300,13 @@ public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exce .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140202", "Numerical analysis", - "MATHEMATICS :: Applied mathematics :: Numerical analysis"), + "MATHEMATICS::Applied mathematics::Numerical analysis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140203", "Mathematical statistics", - "MATHEMATICS :: Applied mathematics :: Mathematical statistics"), + "MATHEMATICS::Applied mathematics::Mathematical statistics"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140204", "Optimization, systems theory", - "MATHEMATICS :: Applied mathematics :: Optimization, systems theory"), + "MATHEMATICS::Applied mathematics::Optimization, systems theory"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140205", "Theoretical computer science", - "MATHEMATICS :: Applied mathematics :: Theoretical computer science") + "MATHEMATICS::Applied mathematics::Theoretical computer science") ))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(4))); } @@ -379,7 +379,7 @@ public void srscProjectionTest() throws Exception { .andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children())) .andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent", Matchers.everyItem( - is("HUMANITIES and RELIGION :: Religion/Theology")))); + is("HUMANITIES and RELIGION::Religion/Theology")))); getClient(tokenAdmin).perform( get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "children")) @@ -387,7 +387,7 @@ public void srscProjectionTest() throws Exception { .andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children())) .andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent", Matchers.everyItem( - is("HUMANITIES and RELIGION :: Religion/Theology")))); + is("HUMANITIES and RELIGION::Religion/Theology")))); getClient(tokenAdmin).perform( get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "parent")) @@ -402,40 +402,40 @@ private Matcher> matchAllSrscSC110Children() { return Matchers.containsInAnyOrder( VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110102", "History of religion", - "HUMANITIES and RELIGION :: Religion/Theology :: History of religion"), + "HUMANITIES and RELIGION::Religion/Theology::History of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110103", "Church studies", - "HUMANITIES and RELIGION :: Religion/Theology :: Church studies"), + "HUMANITIES and RELIGION::Religion/Theology::Church studies"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110104", "Missionary studies", - "HUMANITIES and RELIGION :: Religion/Theology :: Missionary studies"), + "HUMANITIES and RELIGION::Religion/Theology::Missionary studies"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110105", "Systematic theology", - "HUMANITIES and RELIGION :: Religion/Theology :: Systematic theology"), + "HUMANITIES and RELIGION::Religion/Theology::Systematic theology"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110106", "Islamology", - "HUMANITIES and RELIGION :: Religion/Theology :: Islamology"), + "HUMANITIES and RELIGION::Religion/Theology::Islamology"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110107", "Faith and reason", - "HUMANITIES and RELIGION :: Religion/Theology :: Faith and reason"), + "HUMANITIES and RELIGION::Religion/Theology::Faith and reason"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110108", "Sociology of religion", - "HUMANITIES and RELIGION :: Religion/Theology :: Sociology of religion"), + "HUMANITIES and RELIGION::Religion/Theology::Sociology of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110109", "Psychology of religion", - "HUMANITIES and RELIGION :: Religion/Theology :: Psychology of religion"), + "HUMANITIES and RELIGION::Religion/Theology::Psychology of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110110", "Philosophy of religion", - "HUMANITIES and RELIGION :: Religion/Theology :: Philosophy of religion"), + "HUMANITIES and RELIGION::Religion/Theology::Philosophy of religion"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110111", "New Testament exegesis", - "HUMANITIES and RELIGION :: Religion/Theology :: New Testament exegesis"), + "HUMANITIES and RELIGION::Religion/Theology::New Testament exegesis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110112", "Old Testament exegesis", - "HUMANITIES and RELIGION :: Religion/Theology :: Old Testament exegesis"), + "HUMANITIES and RELIGION::Religion/Theology::Old Testament exegesis"), VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110113", "Dogmatics with symbolics", - "HUMANITIES and RELIGION :: Religion/Theology :: Dogmatics with symbolics") + "HUMANITIES and RELIGION::Religion/Theology::Dogmatics with symbolics") ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 5cfcac47d181..f9b31fb06de9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -179,7 +179,7 @@ public void correctSrscQueryTest() throws Exception { VocabularyMatcher.matchVocabularyEntry("Research Subject Categories", "", "vocabularyEntry"), VocabularyMatcher.matchVocabularyEntry("Family research", - "SOCIAL SCIENCES :: Social sciences :: Social work :: Family research", + "SOCIAL SCIENCES::Social sciences::Social work::Family research", "vocabularyEntry")))) .andExpect(jsonPath("$.page.totalElements", Matchers.is(26))) .andExpect(jsonPath("$.page.totalPages", Matchers.is(13))) From 51c581ed26bc16d0249ff69d52ead9abf94c9b9b Mon Sep 17 00:00:00 2001 From: jensroets Date: Tue, 3 Jan 2023 11:02:50 +0100 Subject: [PATCH 170/394] 97414 Advanced workflow actions: implemented feedback on SelectReviewerAction + WorkflowActionRestRepositoryIT --- .../processingaction/RatingReviewAction.java | 3 +- .../SelectReviewerAction.java | 43 +++++++++++++++++-- .../SelectReviewerActionAdvancedInfo.java | 21 +++++---- ...ctReviewerActionAdvancedInfoConverter.java | 2 +- .../SelectReviewerActionAdvancedInfoRest.java | 20 ++++----- .../rest/WorkflowActionRestRepositoryIT.java | 36 +++++++++++++++- .../rest/matcher/WorkflowActionMatcher.java | 30 +++++++++++++ dspace/config/dspace.cfg | 1 + dspace/config/modules/actions.cfg | 2 + 9 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 dspace/config/modules/actions.cfg diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java index 5007b661fe72..53615437e2ba 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewAction.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -70,7 +71,7 @@ protected List getAdvancedOptions() { @Override protected List getAdvancedInfo() { - List advancedInfo = super.getAdvancedInfo(); + List advancedInfo = new ArrayList<>(); RatingReviewActionAdvancedInfo ratingReviewActionAdvancedInfo = new RatingReviewActionAdvancedInfo(); ratingReviewActionAdvancedInfo.setDescriptionRequired(descriptionRequired); ratingReviewActionAdvancedInfo.setMaxValue(maxValue); diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java index 9507245ab795..39d41af4e1db 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java @@ -18,7 +18,10 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; import org.dspace.eperson.service.EPersonService; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; @@ -45,7 +48,7 @@ public class SelectReviewerAction extends ProcessingAction { private static final String SUBMIT_CANCEL = "submit_cancel"; private static final String SUBMIT_SEARCH = "submit_search"; - private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer_"; + private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer"; private Role role; @@ -55,6 +58,12 @@ public class SelectReviewerAction extends ProcessingAction { @Autowired(required = true) private WorkflowItemRoleService workflowItemRoleService; + @Autowired + private ConfigurationService configurationService; + + @Autowired + private GroupService groupService; + @Override public void activate(Context c, XmlWorkflowItem wf) { @@ -91,6 +100,9 @@ public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl //Retrieve the identifier of the eperson which will do the reviewing UUID reviewerId = UUID.fromString(submitButton.substring(submitButton.lastIndexOf("_") + 1)); EPerson reviewer = ePersonService.find(c, reviewerId); + if (!groupService.allMembers(c, getGroup()).contains(reviewer)) { + return new ActionResult(ActionResult.TYPE.TYPE_ERROR); + } //We have a reviewer, assign him, the workflowitemrole will be translated into a task in the autoassign WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c); workflowItemRole.setEPerson(reviewer); @@ -107,7 +119,6 @@ public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl @Override public List getOptions() { List options = new ArrayList<>(); - options.add(SUBMIT_SEARCH); options.add(SUBMIT_SELECT_REVIEWER); return options; } @@ -119,9 +130,9 @@ protected List getAdvancedOptions() { @Override protected List getAdvancedInfo() { - List advancedInfo = super.getAdvancedInfo(); + List advancedInfo = new ArrayList<>(); SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo(); - selectReviewerActionAdvancedInfo.setRole(role); + selectReviewerActionAdvancedInfo.setGroup(getGroup().getID().toString()); selectReviewerActionAdvancedInfo.setType(SUBMIT_SELECT_REVIEWER); selectReviewerActionAdvancedInfo.setId(SUBMIT_SELECT_REVIEWER); advancedInfo.add(selectReviewerActionAdvancedInfo); @@ -136,4 +147,28 @@ public Role getRole() { public void setRole(Role role) { this.role = role; } + + /** + * Get the Reviewer group from the "action.selectrevieweraction.group" property in actions.cfg by its UUID or name + * Returns null if no (valid) group configured + * @return configured reviewers Group from property or null if none + */ + private Group getGroup() { + Context context = new Context(); + String groupIdOrName = configurationService.getProperty("action.selectrevieweraction.group"); + Group group = null; + + try { + // try to get group by name + group = groupService.findByName(context, groupIdOrName); + if (group == null) { + // try to get group by uuid if not a name + group = groupService.find(context, UUID.fromString(groupIdOrName)); + } + } catch (Exception ignored) { + // ignore, there is no reviewer group set + } + + return group; + } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java index 8f88e9e1fd64..c0b121f19d72 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java @@ -7,7 +7,6 @@ */ package org.dspace.xmlworkflow.state.actions.processingaction; -import org.dspace.xmlworkflow.Role; import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; import org.springframework.util.DigestUtils; @@ -16,26 +15,26 @@ * {@link org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction} */ public class SelectReviewerActionAdvancedInfo implements ActionAdvancedInfo { - private Role role; + private String group; private String type; private String id; /** - * Generic getter for the role + * Generic getter for the group * - * @return the role value + * @return the group value */ - public Role getRole() { - return role; + public String getGroup() { + return group; } /** - * Generic setter for the role + * Generic setter for the group * - * @param role The role to be set + * @param group The group to be set */ - public void setRole(Role role) { - this.role = role; + public void setGroup(String group) { + this.group = group; } @Override @@ -56,7 +55,7 @@ public String getId() { @Override public void setId(String type) { String idString = type - + ";role," + role; + + ";group," + group; this.id = DigestUtils.md5DigestAsHex(idString.getBytes()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java index 43127e116205..8d028977af20 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java @@ -23,7 +23,7 @@ public SelectReviewerActionAdvancedInfoRest convert(SelectReviewerActionAdvanced Projection projection) { SelectReviewerActionAdvancedInfoRest restModel = new SelectReviewerActionAdvancedInfoRest(); restModel.setProjection(projection); - restModel.setRole(modelObject.getRole()); + restModel.setGroup(modelObject.getGroup()); restModel.setType(modelObject.getType()); restModel.setId(modelObject.getId()); return restModel; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java index d09fbfb6e6cb..b9f118a0799d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java @@ -7,33 +7,31 @@ */ package org.dspace.app.rest.model; -import org.dspace.xmlworkflow.Role; - /** * The SelectReviewerActionAdvancedInfoRest REST Resource */ public class SelectReviewerActionAdvancedInfoRest extends WorkflowActionRest { - private Role role; + private String groupId; private String type; private String id; /** - * Generic getter for the role + * Generic getter for the group * - * @return the role value of this SelectReviewerActionAdvancedInfoRest + * @return the group value of this SelectReviewerActionAdvancedInfoRest */ - public Role getRole() { - return role; + public String getGroup() { + return groupId; } /** - * Generic setter for the role + * Generic setter for the group uuid * - * @param role The role to be set on this SelectReviewerActionAdvancedInfoRest + * @param groupId The group uuid to be set on this SelectReviewerActionAdvancedInfoRest */ - public void setRole(Role role) { - this.role = role; + public void setGroup(String groupId) { + this.groupId = groupId; } /** diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java index cd0de21e00d3..60885967232b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java @@ -18,9 +18,16 @@ import org.dspace.app.rest.model.WorkflowActionRest; import org.dspace.app.rest.repository.WorkflowActionRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.eperson.Group; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.dspace.xmlworkflow.state.actions.processingaction.RatingReviewActionAdvancedInfo; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo; import org.hamcrest.Matchers; import org.junit.Test; @@ -32,6 +39,8 @@ public class WorkflowActionRestRepositoryIT extends AbstractControllerIntegrationTest { private XmlWorkflowFactory xmlWorkflowFactory = XmlWorkflowServiceFactory.getInstance().getWorkflowFactory(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); private static final String WORKFLOW_ACTIONS_ENDPOINT = "/api/" + WorkflowActionRest.CATEGORY + "/" + WorkflowActionRest.NAME_PLURAL; @@ -134,6 +143,14 @@ public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() thr String token = getAuthToken(eperson.getEmail(), password); String nameActionWithOptions = "ratingreviewaction"; WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); + + // create RatingReviewActionAdvancedInfo to compare with output + RatingReviewActionAdvancedInfo ratingReviewActionAdvancedInfo = new RatingReviewActionAdvancedInfo(); + ratingReviewActionAdvancedInfo.setDescriptionRequired(true); + ratingReviewActionAdvancedInfo.setMaxValue(5); + ratingReviewActionAdvancedInfo.setType("rating"); + ratingReviewActionAdvancedInfo.setId("rating"); + //When we call this facets endpoint getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) //We expect a 200 is ok status @@ -142,7 +159,8 @@ public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() thr .andExpect(jsonPath("$.options", not(empty()))) .andExpect(jsonPath("$.advancedOptions", not(empty()))) .andExpect(jsonPath("$.advanced", is(true))) - .andExpect(jsonPath("$.advancedInfo", not(empty()))) + .andExpect(jsonPath("$.advancedInfo", Matchers.contains( + WorkflowActionMatcher.matchRatingReviewActionAdvancedInfo(ratingReviewActionAdvancedInfo)))) //Matches expected corresponding rest action values .andExpect(jsonPath("$", Matchers.is( WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) @@ -153,6 +171,19 @@ public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() thr public void getWorkflowActionByName_ExistentWithOptions_selectrevieweraction() throws Exception { String token = getAuthToken(eperson.getEmail(), password); String nameActionWithOptions = "selectrevieweraction"; + // create reviewers group + context.turnOffAuthorisationSystem(); + Group group = groupService.create(context); + configurationService.setProperty("action.selectrevieweraction.group", group.getID()); + context.restoreAuthSystemState(); + + // create SelectReviewerActionAdvancedInfo to compare with output + SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo(); + selectReviewerActionAdvancedInfo.setGroup(group.getID().toString()); + selectReviewerActionAdvancedInfo.setType("submit_select_reviewer"); + selectReviewerActionAdvancedInfo.setId("submit_select_reviewer"); + + WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); //When we call this facets endpoint getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) @@ -162,7 +193,8 @@ public void getWorkflowActionByName_ExistentWithOptions_selectrevieweraction() t .andExpect(jsonPath("$.options", not(empty()))) .andExpect(jsonPath("$.advancedOptions", not(empty()))) .andExpect(jsonPath("$.advanced", is(true))) - .andExpect(jsonPath("$.advancedInfo", not(empty()))) + .andExpect(jsonPath("$.advancedInfo", Matchers.contains( + WorkflowActionMatcher.matchSelectReviewerActionAdvancedInfo(selectReviewerActionAdvancedInfo)))) //Matches expected corresponding rest action values .andExpect(jsonPath("$", Matchers.is( WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java index 652cbff5dc62..781b95fd9efd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java @@ -14,7 +14,10 @@ import org.dspace.app.rest.model.WorkflowActionRest; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.dspace.xmlworkflow.state.actions.processingaction.RatingReviewActionAdvancedInfo; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo; import org.hamcrest.Matcher; +import org.hamcrest.Matchers; /** * @author Maria Verdonck (Atmire) on 06/01/2020 @@ -36,4 +39,31 @@ public static Matcher matchWorkflowActionEntry(WorkflowActionCon hasJsonPath("$._links.self.href", containsString(WORKFLOW_ACTIONS_ENDPOINT + workflowAction.getId())) ); } + + /** + * Matcher to check the contents of the advancedInfo for "ratingreviewaction" + * @param ratingReviewActionAdvancedInfo identical RatingReviewActionAdvancedInfo object + */ + public static Matcher matchRatingReviewActionAdvancedInfo( + RatingReviewActionAdvancedInfo ratingReviewActionAdvancedInfo) { + return Matchers.allOf( + hasJsonPath("$.descriptionRequired", is(ratingReviewActionAdvancedInfo.isDescriptionRequired())), + hasJsonPath("$.maxValue", is(ratingReviewActionAdvancedInfo.getMaxValue())), + hasJsonPath("$.type", is(ratingReviewActionAdvancedInfo.getType())), + hasJsonPath("$.id", is(ratingReviewActionAdvancedInfo.getId())) + ); + } + + /** + * Matcher to check the contents of the advancedInfo for "selectrevieweraction" + * @param selectReviewerActionAdvancedInfo identical SelectReviewerActionAdvancedInfo object + */ + public static Matcher matchSelectReviewerActionAdvancedInfo( + SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo) { + return Matchers.allOf( + hasJsonPath("$.group", is(selectReviewerActionAdvancedInfo.getGroup())), + hasJsonPath("$.type", is(selectReviewerActionAdvancedInfo.getType())), + hasJsonPath("$.id", is(selectReviewerActionAdvancedInfo.getId())) + ); + } } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 02c618abf420..9a839d701ecd 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1576,6 +1576,7 @@ module_dir = modules # PRIOR to those below (and therefore may override configs in these default # module configuration files). +include = ${module_dir}/actions.cfg include = ${module_dir}/altmetrics.cfg include = ${module_dir}/authentication.cfg include = ${module_dir}/authentication-ip.cfg diff --git a/dspace/config/modules/actions.cfg b/dspace/config/modules/actions.cfg new file mode 100644 index 000000000000..aea8fc2a2c8f --- /dev/null +++ b/dspace/config/modules/actions.cfg @@ -0,0 +1,2 @@ +# Reviewer group can be UUID or group name +action.selectrevieweraction.group = Reviewers From bb6e089ae561f80ec2cce69507b1fbd8147fe613 Mon Sep 17 00:00:00 2001 From: jensroets Date: Tue, 3 Jan 2023 12:40:09 +0100 Subject: [PATCH 171/394] 97414 Advanced workflow actions: fixed SelectReviewerAction NullPointerException --- .../state/actions/processingaction/SelectReviewerAction.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java index 39d41af4e1db..1833510f9256 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java @@ -132,7 +132,9 @@ protected List getAdvancedOptions() { protected List getAdvancedInfo() { List advancedInfo = new ArrayList<>(); SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo(); - selectReviewerActionAdvancedInfo.setGroup(getGroup().getID().toString()); + if (getGroup() != null) { + selectReviewerActionAdvancedInfo.setGroup(getGroup().getID().toString()); + } selectReviewerActionAdvancedInfo.setType(SUBMIT_SELECT_REVIEWER); selectReviewerActionAdvancedInfo.setId(SUBMIT_SELECT_REVIEWER); advancedInfo.add(selectReviewerActionAdvancedInfo); From d1a7fe9a02ac69a15d3a29daea5715974cfe1532 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Tue, 3 Jan 2023 13:07:35 +0100 Subject: [PATCH 172/394] [CST-7604] Configuration updated to highlight Orgunit and Person --- dspace/config/spring/api/discovery.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 57f4c07aee5c..ab333277c4c0 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -247,6 +247,18 @@ + + + + + + + + + + + + + From 1f06e29c256c25b97aaff9c055933afc6389fddc Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 20 Jan 2023 11:49:29 +0100 Subject: [PATCH 190/394] Implemented community feedbacks --- .../org/dspace/subscriptions/ContentGenerator.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index 0365d6396dd6..a01a6e33a45e 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -58,18 +58,18 @@ public void notifyForSubscriptions(Context context, EPerson ePerson, Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson); Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscriptions_content")); email.addRecipient(ePerson.getEmail()); - email.addArgument(generateHtmlBodyMail(context, indexableComm)); - email.addArgument(generateHtmlBodyMail(context, indexableColl)); - email.addArgument(generateHtmlBodyMail(context, indexableItems)); + email.addArgument(generateBodyMail(context, indexableComm)); + email.addArgument(generateBodyMail(context, indexableColl)); + email.addArgument(generateBodyMail(context, indexableItems)); email.send(); } } catch (Exception e) { - // log this email error - log.warn("Cannot email user eperson_id:" + ePerson.getID() + " eperson_email:" + ePerson.getEmail()); + log.error(e.getMessage(), e); + log.warn("Cannot email user eperson_id: {} eperson_email: {}", ePerson::getID, ePerson::getEmail); } } - private String generateHtmlBodyMail(Context context, List indexableObjects) { + private String generateBodyMail(Context context, List indexableObjects) { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); out.write("\n".getBytes(UTF_8)); From 51424cbb7c4511fefd90d28718b74059dc7a651b Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 23 Jan 2023 17:22:32 +0100 Subject: [PATCH 191/394] 97425: Fix issue with sessions null value --- .../src/main/java/org/dspace/alerts/SystemWideAlert.java | 4 ++-- .../java/org/dspace/alerts/SystemWideAlertServiceImpl.java | 2 +- .../app/rest/repository/SystemWideAlertRestRepository.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java index 78075511d4ad..debf683d6492 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java @@ -106,8 +106,8 @@ public int getAllowSessions() { * * @param allowSessions Integer representing what kind of sessions are allowed */ - public void setAllowSessions(final int allowSessions) { - this.allowSessions = allowSessions; + public void setAllowSessions(AllowSessionsEnum allowSessions) { + this.allowSessions = allowSessions.getValue(); } /** diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java index 73a130960e9c..cc59289e42b2 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java @@ -45,7 +45,7 @@ public SystemWideAlert create(final Context context, final String message, final Date countdownTo, final boolean active) throws SQLException { SystemWideAlert systemWideAlert = new SystemWideAlert(); systemWideAlert.setMessage(message); - systemWideAlert.setAllowSessions(allowSessionsType.getValue()); + systemWideAlert.setAllowSessions(allowSessionsType); systemWideAlert.setCountdownTo(countdownTo); systemWideAlert.setActive(active); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java index 7fb451e7fdbc..bdca9eaf6972 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java @@ -105,7 +105,7 @@ protected SystemWideAlertRest put(Context context, HttpServletRequest request, S } systemWideAlert.setMessage(systemWideAlertRest.getMessage()); - systemWideAlert.setAllowSessions(systemWideAlertRest.getAllowSessions()); + systemWideAlert.setAllowSessions(AllowSessionsEnum.fromInt(systemWideAlertRest.getAllowSessions())); systemWideAlert.setCountdownTo(systemWideAlertRest.getCountdownTo()); systemWideAlert.setActive(systemWideAlertRest.isActive()); From f1af02c9cc0d3ef9b46045809a65aebd824d5457 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 25 Jan 2023 15:40:53 +0100 Subject: [PATCH 192/394] 97061: type request param name change to avoid confusion with rest object type & error codes endpoint changes + IT: - missing/wrong query param accountRequestType => 400 bad request - email not valid domain => 422 unprocessable entity --- .../RegistrationRestRepository.java | 16 ++++++---- .../rest/RegistrationRestRepositoryIT.java | 32 ++++++++++++++++--- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java index d1d13ded7c5e..f019f0c85c9c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -47,7 +47,7 @@ public class RegistrationRestRepository extends DSpaceRestRepository registrationDataList = new ArrayList<>(); + public void testRegisterDomainNotRegistered() throws Exception { + List registrationDataList; try { configurationService.setProperty("authentication-password.domain.valid", "test.com"); RegistrationRest registrationRest = new RegistrationRest(); @@ -204,7 +203,7 @@ public void testRegisterDomainNotRegistred() throws Exception { .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) - .andExpect(status().isBadRequest()); + .andExpect(status().isUnprocessableEntity()); } finally { registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); Iterator iterator = registrationDataList.iterator(); @@ -235,7 +234,7 @@ public void testRegisterDomainNotRegisteredMailAddressRegistred() throws Excepti .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest)) .contentType(contentType)) - .andExpect(status().isBadRequest()); + .andExpect(status().isUnprocessableEntity()); registrationDataList = registrationDataDAO.findAll(context, RegistrationData.class); assertEquals(0, registrationDataList.size()); } finally { @@ -275,4 +274,27 @@ public void forgotPasswordTest() throws Exception { } } + @Test + public void accountEndpoint_WithoutAccountTypeParam() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + @Test + public void accountEndpoint_WrongAccountTypeParam() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + RegistrationRest registrationRest = new RegistrationRest(); + registrationRest.setEmail(eperson.getEmail()); + getClient().perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, "nonValidValue") + .content(mapper.writeValueAsBytes(registrationRest)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + } From f4ca5958ac0ba00dd177e95a255ef04eb359967b Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 25 Jan 2023 18:37:01 +0100 Subject: [PATCH 193/394] [CST-7756] refactoring & added missing JavaDoc --- .../factory/ContentServiceFactory.java | 5 +- .../SubscribeParameterServiceImpl.java | 76 ------------- .../dspace/eperson/SubscribeServiceImpl.java | 11 +- .../java/org/dspace/eperson/Subscription.java | 15 ++- .../dspace/eperson/SubscriptionParameter.java | 8 ++ .../dspace/eperson/dao/SubscriptionDAO.java | 104 +++++++++++++++++- .../eperson/dao/impl/SubscriptionDAOImpl.java | 7 +- .../eperson/service/SubscribeService.java | 14 +-- .../service/SubscriptionParameterService.java | 82 -------------- .../SubscriptionEmailNotification.java | 8 +- ...riptionEmailNotificationConfiguration.java | 3 +- .../SubscriptionEmailNotificationService.java | 23 ++-- .../rest/converter/SubscriptionConverter.java | 3 +- .../app/rest/matcher/SubscriptionMatcher.java | 9 +- 14 files changed, 160 insertions(+), 208 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java delete mode 100644 dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java index 8a83976b6662..a56dda41c6ea 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java @@ -117,10 +117,7 @@ public InProgressSubmissionService getInProgressSubmissionService(InProgressSubm } public DSpaceObjectService getDSpaceObjectService(T dso) { - // No need to worry when supressing, as long as our "getDSpaceObjectManager" method is properly implemented - // no casting issues should occur - DSpaceObjectService manager = getDSpaceObjectService(dso.getType()); - return manager; + return getDSpaceObjectService(dso.getType()); } @SuppressWarnings("unchecked") diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java deleted file mode 100644 index 81c867f92120..000000000000 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeParameterServiceImpl.java +++ /dev/null @@ -1,76 +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.eperson; - -import java.sql.SQLException; -import java.util.List; -import java.util.Objects; - -import org.dspace.core.Context; -import org.dspace.eperson.dao.SubscriptionParameterDAO; -import org.dspace.eperson.service.SubscriptionParameterService; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * Class implemennting method for service layer of SubscriptionParameter entity - * - * @author Alba Aliu at atis.al - */ -public class SubscribeParameterServiceImpl implements SubscriptionParameterService { - - @Autowired(required = true) - protected SubscriptionParameterDAO subscriptionParameterDAO; - - protected SubscribeParameterServiceImpl() {} - - @Override - public List findAll(Context context) throws SQLException { - return subscriptionParameterDAO.findAll(context, SubscriptionParameter.class); - } - - @Override - public SubscriptionParameter add(Context context, String name, String value, Subscription subscription) - throws SQLException { - SubscriptionParameter subscriptionParameter = - subscriptionParameterDAO.create(context, new SubscriptionParameter()); - subscriptionParameter.setName(name); - subscriptionParameter.setSubscription(subscription); - subscriptionParameter.setValue(value); - return subscriptionParameter; - } - - @Override - public SubscriptionParameter edit(Context context, Integer id, String value, String name, Subscription subscription) - throws SQLException { - SubscriptionParameter subscriptionParameter = - subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); - subscriptionParameter.setName(name); - subscriptionParameter.setSubscription(subscription); - subscriptionParameter.setValue(value); - subscriptionParameterDAO.save(context, subscriptionParameter); - return subscriptionParameter; - } - - @Override - public SubscriptionParameter findById(Context context, int id) throws SQLException { - return subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); - } - - @Override - public void delete(Context context, Integer id) throws SQLException { - SubscriptionParameter subscriptionParameter = - subscriptionParameterDAO.findByID(context, SubscriptionParameter.class, id); - if (Objects.nonNull(subscriptionParameter)) { - subscriptionParameter.setSubscription(null); - subscriptionParameterDAO.delete(context, subscriptionParameter); - } else { - throw new SQLException("Subscription parameter with id" + id + "do not exists"); - } - } - -} diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 6ed9ca2d5acb..b54e7c5ec674 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -72,7 +72,7 @@ public Subscription subscribe(Context context, EPerson eperson, newSubscription.addParameter(subscriptionParameter)); newSubscription.setePerson(eperson); newSubscription.setdSpaceObject(dSpaceObject); - newSubscription.setType(type); + newSubscription.setSubscriptionType(type); return newSubscription; } else { throw new AuthorizeException("Only admin or e-person themselves can subscribe"); @@ -153,7 +153,7 @@ public Subscription updateSubscription(Context context, Integer id, EPerson eper throws SQLException { Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); subscriptionDB.removeParameterList(); - subscriptionDB.setType(type); + subscriptionDB.setSubscriptionType(type); subscriptionDB.setdSpaceObject(dSpaceObject); subscriptionParameterList.forEach(x -> subscriptionDB.addParameter(x)); subscriptionDB.setePerson(eperson); @@ -185,9 +185,10 @@ public void deleteSubscription(Context context, Subscription subscription) throw } @Override - public List findAllSubscriptionsByTypeAndFrequency(Context context,String type, String frequencyValue) - throws SQLException { - return subscriptionDAO.findAllSubscriptionsByTypeAndFrequency(context, type, frequencyValue); + public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequencyValue) throws SQLException { + return subscriptionDAO.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, + frequencyValue); } @Override diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index 1f5ead43873f..c706a1db60ad 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -49,8 +49,13 @@ public class Subscription implements ReloadableEntity { @JoinColumn(name = "eperson_id") private EPerson ePerson; + /** + * Represent subscription type, for example, "content" or "statistics". + * + * NOTE: Currently, in DSpace we use only one "content" + */ @Column(name = "type") - private String type; + private String subscriptionType; @OneToMany(fetch = FetchType.LAZY, mappedBy = "subscription", cascade = CascadeType.ALL, orphanRemoval = true) private List subscriptionParameterList = new ArrayList<>(); @@ -86,12 +91,12 @@ public void setdSpaceObject(DSpaceObject dSpaceObject) { this.dSpaceObject = dSpaceObject; } - public String getType() { - return type; + public String getSubscriptionType() { + return subscriptionType; } - public void setType(String type) { - this.type = type; + public void setSubscriptionType(String subscriptionType) { + this.subscriptionType = subscriptionType; } public List getSubscriptionParameterList() { diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java index 590374802cce..7526535d7fcd 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscriptionParameter.java @@ -21,6 +21,7 @@ /** * Database entity representation of the subscription_parameter table + * SubscriptionParameter represents a frequency with which an user wants to be notified. * * @author Alba Aliu at atis.al */ @@ -39,9 +40,16 @@ public class SubscriptionParameter implements ReloadableEntity { @JoinColumn(name = "subscription_id", nullable = false) private Subscription subscription; + /* + * Currently, we have only one use case for this attribute: "frequency" + */ @Column private String name; + /* + * Currently, we use this attribute only with following values: "D", "W", "M". + * Where D stand for Day, W stand for Week and M stand for Month + */ @Column private String value; diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index 011486622b5a..e1c589077eef 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -26,31 +26,125 @@ */ public interface SubscriptionDAO extends GenericDAO { + /** + * Delete all subscription of provided dSpaceObject + * + * @param context DSpace context object + * @param dSpaceObject DSpace resource + * @throws SQLException If database error + */ public void deleteByDspaceObject(Context context, DSpaceObject dSpaceObject) throws SQLException; + /** + * Return a paginated list of all subscriptions of the eperson + * + * @param context DSpace context object + * @param eperson ePerson whose subscriptions want to find + * @param limit Paging limit + * @param offset The position of the first result to return + * @return + * @throws SQLException If database error + */ public List findByEPerson(Context context, EPerson eperson, Integer limit, Integer offset) throws SQLException; - public List findByEPersonAndDso(Context context,EPerson eperson, DSpaceObject dSpaceObject, + /** + * Return a paginated list of subscriptions related to a DSpaceObject belong to an ePerson + * + * @param context DSpace context object + * @param eperson ePerson whose subscriptions want to find + * @param dSpaceObject DSpaceObject of whom subscriptions want to find + * @param limit Paging limit + * @param offset The position of the first result to return + * @return + * @throws SQLException If database error + */ + public List findByEPersonAndDso(Context context, EPerson eperson, DSpaceObject dSpaceObject, Integer limit, Integer offset) throws SQLException; + /** + * Delete all subscription of provided ePerson + * + * @param context DSpace context object + * @param eperson ePerson whose subscriptions want to delete + * @throws SQLException If database error + */ public void deleteByEPerson(Context context, EPerson eperson) throws SQLException; - public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) - throws SQLException; + /** + * Delete all subscriptions related to a DSpaceObject belong to an ePerson + * + * @param context DSpace context object + * @param dSpaceObject DSpaceObject of whom subscriptions want to delete + * @param eperson ePerson whose subscriptions want to delete + * @throws SQLException If database error + */ + public void deleteByDSOAndEPerson(Context context, DSpaceObject dSpaceObject, EPerson eperson) throws SQLException; + /** + * Return a paginated list of all subscriptions ordered by ID and resourceType + * + * @param context DSpace context object + * @param resourceType Could be Item, Collection or Community + * @param limit Paging limit + * @param offset The position of the first result to return + * @return + * @throws SQLException If database error + */ public List findAllOrderedByIDAndResourceType(Context context, String resourceType, Integer limit, Integer offset) throws SQLException; + /** + * Return a paginated list of subscriptions ordered by DSpaceObject + * + * @param context DSpace context object + * @param limit Paging limit + * @param offset The position of the first result to return + * @return + * @throws SQLException If database error + */ public List findAllOrderedByDSO(Context context, Integer limit, Integer offset) throws SQLException; - public List findAllSubscriptionsByTypeAndFrequency(Context context, String type,String frequencyValue) - throws SQLException; + /** + * Return a list of all subscriptions by subscriptionType and frequency + * + * @param context DSpace context object + * @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content" + * @param frequencyValue Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month + * @return + * @throws SQLException If database error + */ + public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequencyValue) throws SQLException; + /** + * Count all subscriptions + * + * @param context DSpace context object + * @return Total of all subscriptions + * @throws SQLException If database error + */ public Long countAll(Context context) throws SQLException; + /** + * Count all subscriptions belong to an ePerson + * + * @param context DSpace context object + * @param ePerson ePerson whose subscriptions want count + * @return Total of all subscriptions belong to an ePerson + * @throws SQLException If database error + */ public Long countAllByEPerson(Context context, EPerson ePerson) throws SQLException; + /** + * Count all subscriptions related to a DSpaceObject belong to an ePerson + * + * @param context DSpace context object + * @param ePerson ePerson whose subscriptions want count + * @param dSpaceObject DSpaceObject of whom subscriptions want count + * @return + * @throws SQLException If database error + */ public Long countAllByEPersonAndDso(Context context, EPerson ePerson,DSpaceObject dSpaceObject) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index 4133b09f4777..e8698d6e925e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -132,14 +132,15 @@ public List findAllOrderedByDSO(Context context, Integer limit, In } @Override - public List findAllSubscriptionsByTypeAndFrequency(Context context, - String type, String frequencyValue) throws SQLException { + public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequencyValue) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Subscription.class); Root subscriptionRoot = criteriaQuery.from(Subscription.class); criteriaQuery.select(subscriptionRoot); Join childJoin = subscriptionRoot.join("subscriptionParameterList"); - criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.TYPE), type), + criteriaQuery.where( + criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.TYPE), subscriptionType), criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.name), "frequency"), criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.value), frequencyValue) )); diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index 6e5e6c66b9aa..c634088d7172 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -200,15 +200,15 @@ public Subscription removeSubscriptionParameter(Context context, Integer id, public void deleteSubscription(Context context, Subscription subscription) throws SQLException; /** - * Finds all subscriptions having given type and frequency + * Finds all subscriptions by subscriptionType and frequency * - * @param context DSpace context - * @param type String type of subscription - * @param frequencyValue String frequency value of subscription - * @throws SQLException An exception that provides information on a database access error or other errors. + * @param context DSpace context + * @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content" + * @param frequencyValue Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month + * @throws SQLException An exception that provides information on a database access error or other errors. */ - public List findAllSubscriptionsByTypeAndFrequency(Context context, String type, - String frequencyValue) throws SQLException; + public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequencyValue) throws SQLException; /** * Counts all subscriptions diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java deleted file mode 100644 index ff6f2b447d49..000000000000 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscriptionParameterService.java +++ /dev/null @@ -1,82 +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.eperson.service; - -import java.sql.SQLException; -import java.util.List; - -import org.dspace.authorize.AuthorizeException; -import org.dspace.core.Context; -import org.dspace.eperson.Subscription; -import org.dspace.eperson.SubscriptionParameter; - -/** - * Service interface class for the SubscriptionParameter object. - * The implementation of this class is responsible for all business logic calls for - * the SubscriptionParameter object and is autowired by spring - * Class defining methods for sending new item e-mail alerts to users - * - * @author Alba Aliu @atis.al - */ -public interface SubscriptionParameterService { - /** - * Finds all of subscriptions parameter - * - * @param context DSpace context - * @return list of Subscription objects - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - public List findAll(Context context) throws SQLException; - - /** - * Adds a new subscription parameter related with a subscription - * - * @param context DSpace context - * @param value String value - * @param name String name - * @param subscription Subscription subscription - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - public SubscriptionParameter add(Context context, String value, String name, Subscription subscription) - throws SQLException; - - /** - * Updates a subscription parameter with id - * - * @param context DSpace context - * @param id Integer id - * @param value String value - * @param name String name - * @param subscription Subscription subscription - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - public SubscriptionParameter edit(Context context, Integer id, String value, String name, Subscription subscription) - throws SQLException; - - /** - * Finds a subscriptionParameter by id - * - * @param context DSpace context - * @param id the id of subscriptionParameter to be searched - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - public SubscriptionParameter findById(Context context, int id) throws SQLException; - - /** - * Deletes a subscriptionParameter with id - * - * @param context DSpace context - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - public void delete(Context context, Integer id) throws SQLException; - -} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java index 3d576b6f16ef..8f783b949e76 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java @@ -7,6 +7,8 @@ */ package org.dspace.subscriptions; +import static org.dspace.subscriptions.SubscriptionEmailNotificationService.FREQUENCIES; + import java.sql.SQLException; import java.util.Objects; import java.util.UUID; @@ -51,13 +53,13 @@ public void internalRun() throws Exception { if (StringUtils.isBlank(frequencyOption)) { throw new IllegalArgumentException("Option frequency f must be set"); } - if (!frequencyOption.equals("D") && !frequencyOption.equals("M") && !frequencyOption.equals("W")) { - throw new IllegalArgumentException("Option f must be D, M or W"); + if (!FREQUENCIES.contains(frequencyOption)) { + throw new IllegalArgumentException("Option f must be one of following values:" + FREQUENCIES); } subscriptionEmailNotificationService.perform(getContext(), handler, "content", frequencyOption); } - protected void assignCurrentUserInContext() throws SQLException { + private void assignCurrentUserInContext() throws SQLException { context = new Context(); UUID uuid = getEpersonIdentifier(); if (Objects.nonNull(uuid)) { diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java index 0ff81bcb56c8..1bd39bb765b4 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java @@ -42,7 +42,8 @@ public boolean isAllowedToExecute(Context context) { public Options getOptions() { if (Objects.isNull(options)) { Options options = new Options(); - options.addOption("f", "Frequency", true, "Subscription frequency. It can have value D, M or W"); + options.addOption("f", "Frequency", true, "Subscription frequency." + + " It can have value D stand for 'Day', W stand for 'Week' and M stand for 'Month'"); options.getOption("f").setRequired(true); super.options = options; } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java index 196ee84128f4..ac21540204d9 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java @@ -61,13 +61,14 @@ public class SubscriptionEmailNotificationService { public void perform(Context context, DSpaceRunnableHandler handler, String type, String frequency) { try { context.turnOffAuthorisationSystem(); - List subscriptionList = findAllSubscriptionsByTypeAndFrequency(context, type, frequency); + List subscriptions = findAllSubscriptionsBySubscriptionTypeAndFrequency(context, type, + frequency); // if content subscription // Here is verified if type is "content" Or "statistics" as them are configured if (type.equals(generators.keySet().toArray()[0])) { // the list of the person who has subscribed int iterator = 0; - for (Subscription subscription : subscriptionList) { + for (Subscription subscription : subscriptions) { DSpaceObject dSpaceObject = getdSpaceObject(subscription); if (dSpaceObject instanceof Community) { communities.addAll(contentUpdates.get(Community.class.getSimpleName().toLowerCase(Locale.ROOT)) @@ -80,8 +81,8 @@ public void perform(Context context, DSpaceRunnableHandler handler, String type, .findUpdates(context, dSpaceObject, frequency)); } var ePerson = subscription.getePerson(); - if (iterator < subscriptionList.size() - 1) { - if (ePerson.equals(subscriptionList.get(iterator + 1).getePerson())) { + if (iterator < subscriptions.size() - 1) { + if (ePerson.equals(subscriptions.get(iterator + 1).getePerson())) { iterator++; continue; } else { @@ -120,14 +121,16 @@ private DSpaceObject getdSpaceObject(Subscription subscription) { return dSpaceObject; } - private List findAllSubscriptionsByTypeAndFrequency(Context context, String type, String frequency) { + private List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequency) { try { - return this.subscribeService.findAllSubscriptionsByTypeAndFrequency(context, type, frequency) - .stream() - .sorted(Comparator.comparing(s -> s.getePerson().getID())) - .collect(Collectors.toList()); + return subscribeService.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, + frequency) + .stream() + .sorted(Comparator.comparing(s -> s.getePerson().getID())) + .collect(Collectors.toList()); } catch (SQLException e) { - log.error(e.getMessage()); + log.error(e.getMessage(), e); } return new ArrayList(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java index fea7fe0e26fc..cd491ecb17bd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubscriptionConverter.java @@ -41,11 +41,10 @@ public SubscriptionRest convert(Subscription subscription, Projection projection SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); subscriptionParameterRest.setName(subscriptionParameter.getName()); subscriptionParameterRest.setValue(subscriptionParameter.getValue()); - subscriptionParameterRest.setId(subscriptionParameter.getID()); subscriptionParameterRestList.add(subscriptionParameterRest); } rest.setSubscriptionParameterList(subscriptionParameterRestList); - rest.setSubscriptionType(subscription.getType()); + rest.setSubscriptionType(subscription.getSubscriptionType()); return rest; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java index ad1df79a8bec..4f3102950c91 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java @@ -31,20 +31,19 @@ public static Matcher matchSubscription(Subscription subscriptio return allOf( hasJsonPath("$.id", is(subscription.getID())), hasJsonPath("$.type", is("subscription")), - hasJsonPath("$.subscriptionType", is(subscription.getType())), + hasJsonPath("$.subscriptionType", is(subscription.getSubscriptionType())), hasJsonPath("$.subscriptionParameterList", Matchers.containsInAnyOrder( subscription.getSubscriptionParameterList().stream() - .map(x -> SubscriptionMatcher.matchSubscriptionParameter(x.getID(), x.getName(), x.getValue())) - .collect(Collectors.toList()) + .map(x -> SubscriptionMatcher.matchSubscriptionParameter(x.getName(), x.getValue())) + .collect(Collectors.toList()) )), //Check links matchLinks(subscription.getID()) ); } - public static Matcher matchSubscriptionParameter(int id, String name, String value) { + public static Matcher matchSubscriptionParameter(String name, String value) { return allOf( - hasJsonPath("$.id", is(id)), hasJsonPath("$.name", is(name)), hasJsonPath("$.value", is(value)) ); From 08259ecb3a06cadd7615d92527de5d4fabaaba56 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 26 Jan 2023 11:53:21 +0100 Subject: [PATCH 194/394] [CST-7756] removed unused service bean --- dspace/config/spring/api/core-services.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index b949eef6e1a3..a124ec830f0c 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -101,7 +101,6 @@ - From 0867a05e79e5157d0aace2439dbd42d3ec50508a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 26 Jan 2023 11:54:57 +0100 Subject: [PATCH 195/394] [CST-7756] minor refactoring according to the rest contract --- .../org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java | 3 ++- .../java/org/dspace/app/rest/model/SubscriptionRest.java | 4 ++-- .../app/rest/repository/SubscriptionRestRepository.java | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java index e8698d6e925e..6c36211f310c 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/impl/SubscriptionDAOImpl.java @@ -140,7 +140,8 @@ public List findAllSubscriptionsBySubscriptionTypeAndFrequency(Con criteriaQuery.select(subscriptionRoot); Join childJoin = subscriptionRoot.join("subscriptionParameterList"); criteriaQuery.where( - criteriaBuilder.and(criteriaBuilder.equal(subscriptionRoot.get(Subscription_.TYPE), subscriptionType), + criteriaBuilder.and( + criteriaBuilder.equal(subscriptionRoot.get(Subscription_.SUBSCRIPTION_TYPE), subscriptionType), criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.name), "frequency"), criteriaBuilder.equal(childJoin.get(SubscriptionParameter_.value), frequencyValue) )); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java index 6e23d850f564..78a81c38b162 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionRest.java @@ -23,8 +23,8 @@ public class SubscriptionRest extends BaseObjectRest { public static final String NAME = "subscription"; public static final String NAME_PLURAL = "subscriptions"; public static final String CATEGORY = "core"; - public static final String DSPACE_OBJECT = "dSpaceObject"; - public static final String EPERSON = "ePerson"; + public static final String DSPACE_OBJECT = "resource"; + public static final String EPERSON = "eperson"; private Integer id; private String subscriptionType; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 2cb7da82d78a..f16c35f4b36f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -120,7 +120,7 @@ public Page findSubscriptionsByEPerson(@Parameter(value = "uui @SearchRestMethod(name = "findByEPersonAndDso") @PreAuthorize("hasPermission(#epersonId, 'AdminOrOwner', 'READ')") public Page findByEPersonAndDso(@Parameter(value = "eperson_id", required = true) UUID epersonId, - @Parameter(value = "dspace_object_id",required = true) UUID dsoId, + @Parameter(value = "resource",required = true) UUID dsoId, Pageable pageable) throws Exception { Long total = null; List subscriptions = null; @@ -142,7 +142,7 @@ public Page findByEPersonAndDso(@Parameter(value = "eperson_id protected SubscriptionRest createAndReturn(Context context) throws SQLException, AuthorizeException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); - String dsoId = req.getParameter("dspace_object_id"); + String dsoId = req.getParameter("resource"); if (Objects.isNull(dsoId) || Objects.isNull(epersonId)) { throw new UnprocessableEntityException("Both eperson than DSpaceObject uuids must be provieded!"); @@ -198,7 +198,7 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); - String dsoId = req.getParameter("dspace_object_id"); + String dsoId = req.getParameter("resource"); SubscriptionRest subscriptionRest; try { From 871bac5d8405c1af6fc4848bde1e6e04162203e8 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 26 Jan 2023 12:15:36 +0100 Subject: [PATCH 196/394] PR #8619: Removed dependency spring-context-support, not needed in main --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index 12767455316f..c66f46c9d7c4 100644 --- a/pom.xml +++ b/pom.xml @@ -1230,12 +1230,6 @@ ${spring.version} - - org.springframework - spring-context-support - ${spring.version} - - org.springframework spring-test From 600a194284ad32890ad9a1df73d902f3d1886e80 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 26 Jan 2023 14:25:06 +0100 Subject: [PATCH 197/394] [CST-7756] fixed failed tests --- .../rest/model/SubscriptionParameterRest.java | 2 + .../rest/SubscriptionRestRepositoryIT.java | 52 +++++++++---------- .../app/rest/matcher/SubscriptionMatcher.java | 4 +- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java index 7d7c5f8c5809..ab815f0d3b84 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubscriptionParameterRest.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.dspace.eperson.Subscription; /** @@ -14,6 +15,7 @@ */ public class SubscriptionParameterRest { + @JsonIgnore private Integer id; private String name; private String value; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index 78dd7f5e4b39..b0e995d168f1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -148,13 +148,13 @@ public void findOneWithOwnerTest() throws Exception { .andExpect(jsonPath("$.subscriptionType", is("TestType"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("/resource"))) .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) - .andExpect(jsonPath("$._links.dSpaceObject.href", + .andExpect(jsonPath("$._links.resource.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", + .andExpect(jsonPath("$._links.eperson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))); } @@ -182,12 +182,12 @@ public void findOneAdminTest() throws Exception { .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) - .andExpect(jsonPath("$._links.dSpaceObject.href", + .andExpect(jsonPath("$._links.resource.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))) - .andExpect(jsonPath("$._links.ePerson.href", + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("/resource"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))); + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))); } @Test @@ -358,7 +358,7 @@ public void createSubscriptionUnauthorizedTest() throws Exception { ObjectMapper objectMapper = new ObjectMapper(); getClient().perform(post("/api/core/subscriptions") - .param("dspace_object_id", publicItem.getID().toString()) + .param("resource", publicItem.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(subscriptionRest)) .contentType(contentType)) @@ -385,7 +385,7 @@ public void createSubscriptionAdminForOtherPersonTest() throws Exception { try { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(post("/api/core/subscriptions") - .param("dspace_object_id", publicItem.getID().toString()) + .param("resource", publicItem.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -393,8 +393,8 @@ public void createSubscriptionAdminForOtherPersonTest() throws Exception { .andExpect(jsonPath("$.subscriptionType", is("testType"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("ePerson"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("resource"))) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); } finally { SubscribeBuilder.deleteSubscription(idRef.get()); @@ -421,7 +421,7 @@ public void createSubscriptionByEPersonTest() throws Exception { try { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("dspace_object_id", publicItem.getID().toString()) + .param("resource", publicItem.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -429,8 +429,8 @@ public void createSubscriptionByEPersonTest() throws Exception { .andExpect(jsonPath("$.subscriptionType", is("testType"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("ePerson"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("dSpaceObject"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("resource"))) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); } finally { SubscribeBuilder.deleteSubscription(idRef.get()); @@ -483,7 +483,7 @@ public void createSubscriptionPersonForAnotherPersonTest() throws Exception { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("dspace_object_id", publicItem.getID().toString()) + .param("resource", publicItem.getID().toString()) .param("eperson_id", user.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -632,7 +632,7 @@ public void putSubscriptionTest() throws Exception { String tokenSubscriber = getAuthToken(eperson.getEmail(), password); getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("dspace_object_id", publicItem.getID().toString()) + .param("resource", publicItem.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -641,8 +641,8 @@ public void putSubscriptionTest() throws Exception { .andExpect(jsonPath("$.subscriptionType", is("test"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("WEEKLY"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))) - .andExpect(jsonPath("$._links.dSpaceObject.href",Matchers.endsWith("/dSpaceObject"))); + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) + .andExpect(jsonPath("$._links.resource.href",Matchers.endsWith("/resource"))); } @Test @@ -669,7 +669,7 @@ public void putSubscriptionAdminTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("dspace_object_id", publicItem.getID().toString()) + .param("resource", publicItem.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -678,8 +678,8 @@ public void putSubscriptionAdminTest() throws Exception { .andExpect(jsonPath("$.subscriptionType", is("test"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("WEEKLY"))) - .andExpect(jsonPath("$._links.ePerson.href", Matchers.endsWith("/ePerson"))) - .andExpect(jsonPath("$._links.dSpaceObject.href", Matchers.endsWith("/dSpaceObject"))); + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("/resource"))); } @Test @@ -695,7 +695,7 @@ public void linkedEpersonOfSubscriptionAdminTest() throws Exception { context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/ePerson")) + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) .andExpect(status().isOk()) .andExpect(jsonPath("$", is(EPersonMatcher.matchEPersonEntry(eperson)))); } @@ -713,7 +713,7 @@ public void linkedEpersonOfSubscriptionTest() throws Exception { context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/ePerson")) + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) .andExpect(status().isOk()) .andExpect(jsonPath("$", is(EPersonMatcher.matchEPersonEntry(eperson)))); } @@ -771,7 +771,7 @@ public void linkedDSpaceObjectOfSubscriptionAdminTest() throws Exception { context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/dSpaceObject")) + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) .andExpect(status().isOk()) .andExpect(jsonPath("$.uuid", Matchers.is(publicItem.getID().toString()))) .andExpect(jsonPath("$.name", Matchers.is(publicItem.getName()))) @@ -794,7 +794,7 @@ public void linkedDSpaceObjectOfSubscriptionTest() throws Exception { context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(eperson.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/dSpaceObject")) + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) .andExpect(status().isOk()) .andExpect(jsonPath("$.uuid", Matchers.is(publicItem.getID().toString()))) .andExpect(jsonPath("$.name", Matchers.is(publicItem.getName()))) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java index 4f3102950c91..88349c73a301 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubscriptionMatcher.java @@ -54,8 +54,8 @@ public static Matcher matchSubscriptionParameter(String name, St */ public static Matcher matchLinks(Integer id) { return HalMatcher.matchLinks(REST_SERVER_URL + "core/subscriptions/" + id, - "dSpaceObject", - "ePerson", + "resource", + "eperson", "self" ); } From 20231fd0867d88521de58b2537e989cab785441c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 26 Jan 2023 18:32:45 +0100 Subject: [PATCH 198/394] [CST-7756] refactoring --- .../SubscriptionEmailNotification.java | 13 +- .../SubscriptionEmailNotificationService.java | 133 +----------- ...scriptionEmailNotificationServiceImpl.java | 169 +++++++++++++++ .../SubscriptionRestRepository.java | 30 ++- .../rest/SubscriptionRestRepositoryIT.java | 195 +++++++++--------- .../api/subscriptions_email_configuration.xml | 15 +- 6 files changed, 321 insertions(+), 234 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java index 8f783b949e76..04c9429c6726 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java @@ -7,10 +7,9 @@ */ package org.dspace.subscriptions; -import static org.dspace.subscriptions.SubscriptionEmailNotificationService.FREQUENCIES; - import java.sql.SQLException; import java.util.Objects; +import java.util.Set; import java.util.UUID; import org.apache.commons.cli.ParseException; @@ -53,8 +52,10 @@ public void internalRun() throws Exception { if (StringUtils.isBlank(frequencyOption)) { throw new IllegalArgumentException("Option frequency f must be set"); } - if (!FREQUENCIES.contains(frequencyOption)) { - throw new IllegalArgumentException("Option f must be one of following values:" + FREQUENCIES); + Set supportedValues = subscriptionEmailNotificationService + .getSubscriptionParameterValuesByName("frequency"); + if (!supportedValues.contains(frequencyOption)) { + throw new IllegalArgumentException("Option f must be one of following values:" + supportedValues); } subscriptionEmailNotificationService.perform(getContext(), handler, "content", frequencyOption); } @@ -78,8 +79,8 @@ public SubscriptionEmailNotificationService getSubscriptionEmailNotificationServ return subscriptionEmailNotificationService; } - public void setSubscriptionEmailNotificationService(SubscriptionEmailNotificationService subscriptionNotifService) { - this.subscriptionEmailNotificationService = subscriptionNotifService; + public void setSubscriptionEmailNotificationService(SubscriptionEmailNotificationService notificationService) { + this.subscriptionEmailNotificationService = notificationService; } public Context getContext() { diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java index ac21540204d9..d0b29b92fec4 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java @@ -7,141 +7,22 @@ */ package org.dspace.subscriptions; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.stream.Collectors; +import java.util.Set; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.core.Context; -import org.dspace.discovery.IndexableObject; -import org.dspace.eperson.Subscription; -import org.dspace.eperson.service.SubscribeService; -import org.dspace.scripts.DSpaceRunnable; import org.dspace.scripts.handler.DSpaceRunnableHandler; -import org.dspace.subscriptions.service.DSpaceObjectUpdates; -import org.dspace.subscriptions.service.SubscriptionGenerator; -import org.hibernate.proxy.HibernateProxy; -import org.hibernate.proxy.LazyInitializer; /** - * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them - * - * @author alba aliu + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ -public class SubscriptionEmailNotificationService { - - private static final Logger log = LogManager.getLogger(SubscriptionEmailNotification.class); - - public static final List FREQUENCIES = Arrays.asList("D", "W", "M"); - - private Map contentUpdates = new HashMap<>(); - @SuppressWarnings("rawtypes") - private Map generators = new HashMap<>(); - @SuppressWarnings("rawtypes") - private List communities = new ArrayList<>(); - @SuppressWarnings("rawtypes") - private List collections = new ArrayList<>(); - @SuppressWarnings("rawtypes") - private List items = new ArrayList<>(); - - private final SubscribeService subscribeService; +public interface SubscriptionEmailNotificationService { - @SuppressWarnings("unchecked") - public void perform(Context context, DSpaceRunnableHandler handler, String type, String frequency) { - try { - context.turnOffAuthorisationSystem(); - List subscriptions = findAllSubscriptionsBySubscriptionTypeAndFrequency(context, type, - frequency); - // if content subscription - // Here is verified if type is "content" Or "statistics" as them are configured - if (type.equals(generators.keySet().toArray()[0])) { - // the list of the person who has subscribed - int iterator = 0; - for (Subscription subscription : subscriptions) { - DSpaceObject dSpaceObject = getdSpaceObject(subscription); - if (dSpaceObject instanceof Community) { - communities.addAll(contentUpdates.get(Community.class.getSimpleName().toLowerCase(Locale.ROOT)) - .findUpdates(context, dSpaceObject, frequency)); - } else if (dSpaceObject instanceof Collection) { - collections.addAll(contentUpdates.get(Collection.class.getSimpleName().toLowerCase(Locale.ROOT)) - .findUpdates(context, dSpaceObject, frequency)); - } else if (dSpaceObject instanceof Item) { - items.addAll(contentUpdates.get(Item.class.getSimpleName().toLowerCase(Locale.ROOT)) - .findUpdates(context, dSpaceObject, frequency)); - } - var ePerson = subscription.getePerson(); - if (iterator < subscriptions.size() - 1) { - if (ePerson.equals(subscriptions.get(iterator + 1).getePerson())) { - iterator++; - continue; - } else { - generators.get(type) - .notifyForSubscriptions(context, ePerson, communities, collections, items); - communities.clear(); - collections.clear(); - items.clear(); - } - } else { - //in the end of the iteration - generators.get(type).notifyForSubscriptions(context, ePerson, communities, collections, items); - } - iterator++; - } - } else { - throw new IllegalArgumentException("Currently this type:" + type + - " of subscription is not supported!"); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - handler.handleException(e); - context.abort(); - } finally { - context.restoreAuthSystemState(); - } - } + public void perform(Context context, DSpaceRunnableHandler handler, String type, String frequency); - private DSpaceObject getdSpaceObject(Subscription subscription) { - DSpaceObject dSpaceObject = subscription.getdSpaceObject(); - if (subscription.getdSpaceObject() instanceof HibernateProxy) { - HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); - LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); - dSpaceObject = (DSpaceObject) initializer.getImplementation(); - } - return dSpaceObject; - } + public Set getSupportedSubscriptionTypes(); - private List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, - String subscriptionType, String frequency) { - try { - return subscribeService.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, - frequency) - .stream() - .sorted(Comparator.comparing(s -> s.getePerson().getID())) - .collect(Collectors.toList()); - } catch (SQLException e) { - log.error(e.getMessage(), e); - } - return new ArrayList(); - } + public boolean isSupportedSubscriptionParameterName(String name); - @SuppressWarnings("rawtypes") - public SubscriptionEmailNotificationService(SubscribeService subscribeService, - Map generators, - Map contentUpdates) { - this.subscribeService = subscribeService; - this.generators = generators; - this.contentUpdates = contentUpdates; - } + public Set getSubscriptionParameterValuesByName(String name); } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java new file mode 100644 index 000000000000..8bf5af06c743 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -0,0 +1,169 @@ +/** + * 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.subscriptions; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.discovery.IndexableObject; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.service.SubscribeService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.dspace.subscriptions.service.DSpaceObjectUpdates; +import org.dspace.subscriptions.service.SubscriptionGenerator; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.LazyInitializer; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them + * + * @author alba aliu + */ +public class SubscriptionEmailNotificationServiceImpl implements SubscriptionEmailNotificationService { + + private static final Logger log = LogManager.getLogger(SubscriptionEmailNotificationServiceImpl.class); + + /** + * The map contains supported {SubscriptionParameter} + * into the key configured {SubscriptionParameter.name} + * instead into value configured a set of values {SubscriptionParameter.value} + * related to {SubscriptionParameter.name} + */ + private Map> param2values = new HashMap<>(); + private Map contentUpdates = new HashMap<>(); + @SuppressWarnings("rawtypes") + private Map subscriptionType2generators = new HashMap<>(); + @SuppressWarnings("rawtypes") + private List communities = new ArrayList<>(); + @SuppressWarnings("rawtypes") + private List collections = new ArrayList<>(); + @SuppressWarnings("rawtypes") + private List items = new ArrayList<>(); + + @Autowired + private SubscribeService subscribeService; + + @SuppressWarnings("rawtypes") + public SubscriptionEmailNotificationServiceImpl(SubscribeService subscribeService, + Map> param2values, + Map contentUpdates, + Map subscriptionType2generators) { + this.param2values = param2values; + this.contentUpdates = contentUpdates; + this.subscribeService = subscribeService; + this.subscriptionType2generators = subscriptionType2generators; + } + + @SuppressWarnings("unchecked") + public void perform(Context context, DSpaceRunnableHandler handler, String type, String frequency) { + try { + List subscriptions = findAllSubscriptionsBySubscriptionTypeAndFrequency(context, type, + frequency); + // Here is verified if type is "content" Or "statistics" as them are configured + if (subscriptionType2generators.keySet().contains(type)) { + // the list of the person who has subscribed + int iterator = 0; + for (Subscription subscription : subscriptions) { + DSpaceObject dSpaceObject = getdSpaceObject(subscription); + if (dSpaceObject instanceof Community) { + communities.addAll(contentUpdates.get(Community.class.getSimpleName().toLowerCase(Locale.ROOT)) + .findUpdates(context, dSpaceObject, frequency)); + } else if (dSpaceObject instanceof Collection) { + collections.addAll(contentUpdates.get(Collection.class.getSimpleName().toLowerCase(Locale.ROOT)) + .findUpdates(context, dSpaceObject, frequency)); + } else if (dSpaceObject instanceof Item) { + items.addAll(contentUpdates.get(Item.class.getSimpleName().toLowerCase(Locale.ROOT)) + .findUpdates(context, dSpaceObject, frequency)); + } + var ePerson = subscription.getePerson(); + if (iterator < subscriptions.size() - 1) { + if (ePerson.equals(subscriptions.get(iterator + 1).getePerson())) { + iterator++; + continue; + } else { + subscriptionType2generators.get(type) + .notifyForSubscriptions(context, ePerson, communities, collections, items); + communities.clear(); + collections.clear(); + items.clear(); + } + } else { + //in the end of the iteration + subscriptionType2generators.get(type) + .notifyForSubscriptions(context, ePerson, communities, collections, items); + } + iterator++; + } + } else { + throw new IllegalArgumentException("Currently this type:" + type + + " of subscription is not supported!"); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + handler.handleException(e); + context.abort(); + } + } + + private DSpaceObject getdSpaceObject(Subscription subscription) { + DSpaceObject dSpaceObject = subscription.getdSpaceObject(); + if (subscription.getdSpaceObject() instanceof HibernateProxy) { + HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); + LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); + dSpaceObject = (DSpaceObject) initializer.getImplementation(); + } + return dSpaceObject; + } + + private List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, + String subscriptionType, String frequency) { + try { + return subscribeService.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, + frequency) + .stream() + .sorted(Comparator.comparing(s -> s.getePerson().getID())) + .collect(Collectors.toList()); + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return new ArrayList(); + } + + @Override + public Set getSupportedSubscriptionTypes() { + return subscriptionType2generators.keySet(); + } + + @Override + public Set getSubscriptionParameterValuesByName(String name) { + return param2values.containsKey(name) ? param2values.get(name) : Collections.emptySet(); + } + + @Override + public boolean isSupportedSubscriptionParameterName(String name) { + return param2values.containsKey(name); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index f16c35f4b36f..69ada3e10636 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections.CollectionUtils; import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; @@ -41,6 +42,7 @@ import org.dspace.eperson.SubscriptionParameter; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.SubscribeService; +import org.dspace.subscriptions.SubscriptionEmailNotificationService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -71,6 +73,8 @@ public class SubscriptionRestRepository extends DSpaceRestRepository subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); - if (subscriptionParameterList != null) { + if (CollectionUtils.isNotEmpty(subscriptionParameterList)) { List subscriptionParameters = new ArrayList<>(); for (SubscriptionParameterRest subscriptionParameterRest : subscriptionParameterList) { SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName(subscriptionParameterRest.getName()); - subscriptionParameter.setValue(subscriptionParameterRest.getValue()); + var name = subscriptionParameterRest.getName(); + var value = subscriptionParameterRest.getValue(); + if (!nameAndValueAreSupported(name, value)) { + throw new UnprocessableEntityException("Provided SubscriptionParameter name:" + name + + " or value: " + value + " is not supported!"); + } + subscriptionParameter.setName(name); + subscriptionParameter.setValue(value); subscriptionParameters.add(subscriptionParameter); } + var type = subscriptionRest.getSubscriptionType(); + if (!subscriptionEmailNotificationService.getSupportedSubscriptionTypes().contains(type)) { + throw new UnprocessableEntityException("Provided subscriptionType:" + type + " is not supported!" + + " Must be one of: " + subscriptionEmailNotificationService.getSupportedSubscriptionTypes()); + } + subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, type); } context.commit(); @@ -191,6 +208,11 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, } } + private boolean nameAndValueAreSupported(String name, String value) { + return subscriptionEmailNotificationService.isSupportedSubscriptionParameterName(name) && + subscriptionEmailNotificationService.getSubscriptionParameterValuesByName(name).contains(value); + } + @Override @PreAuthorize("hasPermission(#id, 'subscription', 'WRITE')") protected SubscriptionRest put(Context context, HttpServletRequest request, String apiCategory, String model, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index b0e995d168f1..b1b1a4453848 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -86,18 +86,18 @@ public void findAll() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); List subscriptionParameterList2 = new ArrayList<>(); SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); - subscriptionParameter2.setName("Frequency"); - subscriptionParameter2.setValue("WEEKLY"); + subscriptionParameter2.setName("frequency"); + subscriptionParameter2.setValue("W"); subscriptionParameterList2.add(subscriptionParameter2); Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, - "TestType 2", collection, admin, subscriptionParameterList2).build(); + "content", collection, admin, subscriptionParameterList2).build(); context.restoreAuthSystemState(); @@ -132,11 +132,11 @@ public void findOneWithOwnerTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -145,9 +145,9 @@ public void findOneWithOwnerTest() throws Exception { //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.subscriptionType", is("TestType"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("M"))) .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("/resource"))) .andExpect(jsonPath("$._links.self.href", @@ -164,11 +164,11 @@ public void findOneAdminTest() throws Exception { List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -177,9 +177,9 @@ public void findOneAdminTest() throws Exception { //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) //By default we expect at least 1 submission forms so this to be reflected in the page object - .andExpect(jsonPath("$.subscriptionType", is("TestType"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Parameter"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("ValueParameter"))) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "core/subscriptions/" + subscription.getID()))) .andExpect(jsonPath("$._links.resource.href", @@ -195,11 +195,11 @@ public void findOneAnonymousTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/subscriptions/" + subscription.getID())) @@ -211,11 +211,11 @@ public void findOneForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -235,18 +235,18 @@ public void findSubscriptionsByEPersonAdminTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter 1"); - subscriptionParameter.setValue("ValueParameter 1"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); List subscriptionParameterList2 = new ArrayList<>(); SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); - subscriptionParameter2.setName("Parameter 2"); - subscriptionParameter2.setValue("ValueParameter 2"); + subscriptionParameter2.setName("frequency"); + subscriptionParameter2.setValue("W"); subscriptionParameterList2.add(subscriptionParameter2); Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, - "TestType 2", collection, eperson, subscriptionParameterList2).build(); + "content", collection, eperson, subscriptionParameterList2).build(); context.restoreAuthSystemState(); @@ -269,18 +269,18 @@ public void findSubscriptionsByEPersonOwnerTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter 1"); - subscriptionParameter.setValue("ValueParameter 1"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); List subscriptionParameterList2 = new ArrayList<>(); SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); - subscriptionParameter2.setName("Parameter 2"); - subscriptionParameter2.setValue("ValueParameter 2"); + subscriptionParameter2.setName("frequency"); + subscriptionParameter2.setValue("D"); subscriptionParameterList2.add(subscriptionParameter2); Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, - "TestType 2", collection, eperson, subscriptionParameterList2).build(); + "content", collection, eperson, subscriptionParameterList2).build(); context.restoreAuthSystemState(); @@ -303,10 +303,10 @@ public void findSubscriptionsByEPersonUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter 1"); - subscriptionParameter.setValue("ValueParameter 1"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, eperson, subscriptionParameterList).build(); + SubscribeBuilder.subscribeBuilder(context, "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/subscriptions/search/findByEPerson") @@ -324,10 +324,10 @@ public void findSubscriptionsByEPersonForbiddenTest() throws Exception { List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter 1"); - subscriptionParameter.setValue("ValueParameter 1"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - SubscribeBuilder.subscribeBuilder(context, "TestType", publicItem, user, subscriptionParameterList).build(); + SubscribeBuilder.subscribeBuilder(context, "content", publicItem, user, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -341,16 +341,16 @@ public void createSubscriptionUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); SubscriptionParameterRest subscriptionParameterRest = new SubscriptionParameterRest(); - subscriptionParameterRest.setValue("Frequency"); - subscriptionParameterRest.setName("Daily"); + subscriptionParameterRest.setValue("frequency"); + subscriptionParameterRest.setName("D"); List subscriptionParameterRestList = new ArrayList<>(); subscriptionParameterRestList.add(subscriptionParameterRest); SubscriptionRest subscriptionRest = new SubscriptionRest(); - subscriptionRest.setSubscriptionType("testType"); + subscriptionRest.setSubscriptionType("content"); MultiValueMap params = new LinkedMultiValueMap(); - params.add("dspace_object_id", publicItem.getID().toString()); + params.add("resource", publicItem.getID().toString()); params.add("eperson_id", eperson.getID().toString()); context.restoreAuthSystemState(); @@ -370,11 +370,11 @@ public void createSubscriptionAdminForOtherPersonTest() throws Exception { context.turnOffAuthorisationSystem(); Map map = new HashMap<>(); - map.put("subscriptionType", "testType"); + map.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "Frequency"); - sub_list.put("value", "daily"); + sub_list.put("name", "frequency"); + sub_list.put("value", "D"); list.add(sub_list); map.put("subscriptionParameterList", list); @@ -390,9 +390,9 @@ public void createSubscriptionAdminForOtherPersonTest() throws Exception { .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isCreated()) - .andExpect(jsonPath("$.subscriptionType", is("testType"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("D"))) .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("eperson"))) .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("resource"))) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); @@ -406,11 +406,11 @@ public void createSubscriptionByEPersonTest() throws Exception { context.turnOffAuthorisationSystem(); Map map = new HashMap<>(); - map.put("subscriptionType", "testType"); + map.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "Frequency"); - sub_list.put("value", "daily"); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); list.add(sub_list); map.put("subscriptionParameterList", list); @@ -426,9 +426,9 @@ public void createSubscriptionByEPersonTest() throws Exception { .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isCreated()) - .andExpect(jsonPath("$.subscriptionType", is("testType"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("daily"))) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("eperson"))) .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("resource"))) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); @@ -442,10 +442,10 @@ public void createInvalidSubscriptionTest() throws Exception { context.turnOffAuthorisationSystem(); Map map = new HashMap<>(); - map.put("type", "testType"); + map.put("type", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "Frequency"); + sub_list.put("name", "frequency"); sub_list.put("value", "daily"); list.add(sub_list); map.put("subscriptionParameterList", list); @@ -454,7 +454,7 @@ public void createInvalidSubscriptionTest() throws Exception { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("dspace_object_id", publicItem.getID().toString()) + .param("resource", publicItem.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -471,11 +471,11 @@ public void createSubscriptionPersonForAnotherPersonTest() throws Exception { .build(); Map map = new HashMap<>(); - map.put("subscriptionType", "testType"); + map.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "Frequency"); - sub_list.put("value", "Daily"); + sub_list.put("name", "frequency"); + sub_list.put("value", "D"); list.add(sub_list); map.put("subscriptionParameterList", list); @@ -495,11 +495,11 @@ public void deleteSubscriptionUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "Test", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(delete("/api/core/subscriptions/" + subscription.getID())) @@ -511,16 +511,19 @@ public void deleteSubscriptionAdminTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "Test", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(delete("/api/core/subscriptions/" + subscription.getID())) .andExpect(status().isNoContent()); + + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID())) + .andExpect(status().isNotFound()); } @Test @@ -528,11 +531,11 @@ public void deleteSubscriptionForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "Test", publicItem, admin, subscriptionParameterList).build(); + "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -556,12 +559,12 @@ public void putSubscriptionUnauthorizedTest() throws Exception { subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); - newSubscription.put("subscriptionType", "test"); + newSubscription.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "frequency"); @@ -570,7 +573,7 @@ public void putSubscriptionUnauthorizedTest() throws Exception { newSubscription.put("subscriptionParameterList", list); getClient().perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("dspace_object_id", publicItem.getID().toString()) + .param("resource", publicItem.getID().toString()) .param("eperson_id", admin.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -582,27 +585,27 @@ public void putSubscriptionForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter1"); - subscriptionParameter.setValue("ValueParameter1"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); - newSubscription.put("subscriptionType", "test"); + newSubscription.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "Frequency"); - sub_list.put("value", "Daily"); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); list.add(sub_list); newSubscription.put("subscriptionParameterList", list); String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(put("/api/core/subscriptions/" + subscription.getID()) .param("eperson_id", admin.getID().toString()) - .param("dspace_object_id", publicItem.getID().toString()) + .param("resource", publicItem.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isForbidden()); @@ -617,12 +620,12 @@ public void putSubscriptionTest() throws Exception { subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); - newSubscription.put("subscriptionType", "test"); + newSubscription.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "Frequency"); @@ -638,7 +641,7 @@ public void putSubscriptionTest() throws Exception { .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$.subscriptionType", is("test"))) + .andExpect(jsonPath("$.subscriptionType", is("content"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("WEEKLY"))) .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) @@ -654,12 +657,12 @@ public void putSubscriptionAdminTest() throws Exception { subscriptionParameter.setValue("Daily"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); Map newSubscription = new HashMap<>(); - newSubscription.put("subscriptionType", "test"); + newSubscription.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); sub_list.put("name", "Frequency"); @@ -675,7 +678,7 @@ public void putSubscriptionAdminTest() throws Exception { .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$.subscriptionType", is("test"))) + .andExpect(jsonPath("$.subscriptionType", is("content"))) .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("WEEKLY"))) .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) @@ -691,7 +694,7 @@ public void linkedEpersonOfSubscriptionAdminTest() throws Exception { subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -709,7 +712,7 @@ public void linkedEpersonOfSubscriptionTest() throws Exception { subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -727,7 +730,7 @@ public void linkedEpersonOfSubscriptionUnauthorizedTest() throws Exception { subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/ePerson")) @@ -743,7 +746,7 @@ public void linkedEpersonOfSubscriptionForbiddenTest() throws Exception { subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -790,7 +793,7 @@ public void linkedDSpaceObjectOfSubscriptionTest() throws Exception { subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(eperson.getEmail(), password); @@ -813,7 +816,7 @@ public void linkedDSpaceObjectOfSubscriptionUnauthorizedTest() throws Exception subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/dSpaceObject")) @@ -829,7 +832,7 @@ public void linkedDSpaceObjectOfSubscriptionForbiddenTest() throws Exception { subscriptionParameter.setValue("ValueParameter"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, admin, subscriptionParameterList).build(); + "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index 97c303ec5450..42d67632720c 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -16,8 +16,8 @@ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> - - + + @@ -29,8 +29,19 @@ + + + + + + + D + W + M + + From d3c5e7debeeadfbece365231378dc03a838b67c4 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 27 Jan 2023 11:51:31 +0100 Subject: [PATCH 199/394] [CST-7756] added tests to valid subscription parameters --- .../rest/SubscriptionRestRepositoryIT.java | 237 +++++++++++++++--- 1 file changed, 201 insertions(+), 36 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index b1b1a4453848..091ff5ce684d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -438,7 +438,79 @@ public void createSubscriptionByEPersonTest() throws Exception { } @Test - public void createInvalidSubscriptionTest() throws Exception { + public void createSubscriptionWithWrongSubscriptionParameterNameTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "TestName"); + sub_list.put("value", "X"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSubscriptionWithInvalidSubscriptionParameterValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "X"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSubscriptionWithInvalidSubscriptionTypeValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "InvalidValue"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSubscriptionInvalidJsonTest() throws Exception { context.turnOffAuthorisationSystem(); Map map = new HashMap<>(); @@ -616,8 +688,8 @@ public void putSubscriptionTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "content", publicItem, eperson, subscriptionParameterList).build(); @@ -628,8 +700,8 @@ public void putSubscriptionTest() throws Exception { newSubscription.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "Frequency"); - sub_list.put("value", "WEEKLY"); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); list.add(sub_list); newSubscription.put("subscriptionParameterList", list); @@ -642,19 +714,112 @@ public void putSubscriptionTest() throws Exception { .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.subscriptionType", is("content"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("WEEKLY"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) .andExpect(jsonPath("$._links.resource.href",Matchers.endsWith("/resource"))); } + @Test + public void putSubscriptionInvalidSubscriptionParameterNameTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "InvalidName"); + sub_list.put("value", "W"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("resource", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void putSubscriptionInvalidSubscriptionParameterValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "Y"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("resource", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void putSubscriptionInvalidSubscriptionTypeValueTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", publicItem, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + ObjectMapper objectMapper = new ObjectMapper(); + Map newSubscription = new HashMap<>(); + newSubscription.put("subscriptionType", "InvalidType"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "D"); + list.add(sub_list); + newSubscription.put("subscriptionParameterList", list); + + String tokenSubscriber = getAuthToken(eperson.getEmail(), password); + getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) + .param("resource", publicItem.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(objectMapper.writeValueAsString(newSubscription)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + @Test public void putSubscriptionAdminTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Frequency"); - subscriptionParameter.setValue("Daily"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "content", publicItem, eperson, subscriptionParameterList).build(); @@ -665,8 +830,8 @@ public void putSubscriptionAdminTest() throws Exception { newSubscription.put("subscriptionType", "content"); List> list = new ArrayList<>(); Map sub_list = new HashMap<>(); - sub_list.put("name", "Frequency"); - sub_list.put("value", "WEEKLY"); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); list.add(sub_list); newSubscription.put("subscriptionParameterList", list); @@ -679,8 +844,8 @@ public void putSubscriptionAdminTest() throws Exception { .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.subscriptionType", is("content"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("Frequency"))) - .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("WEEKLY"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("/eperson"))) .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("/resource"))); } @@ -690,8 +855,8 @@ public void linkedEpersonOfSubscriptionAdminTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "content", publicItem, eperson, subscriptionParameterList).build(); @@ -708,8 +873,8 @@ public void linkedEpersonOfSubscriptionTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "content", publicItem, eperson, subscriptionParameterList).build(); @@ -726,14 +891,14 @@ public void linkedEpersonOfSubscriptionUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/ePerson")) + getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) .andExpect(status().isUnauthorized()); } @@ -742,22 +907,22 @@ public void linkedEpersonOfSubscriptionForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/ePerson")) + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) .andExpect(status().isForbidden()); } @Test public void linkedEpersonOfSubscriptionNotFoundTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE + "/ePerson")) + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE + "/eperson")) .andExpect(status().isNotFound()); } @@ -766,11 +931,11 @@ public void linkedDSpaceObjectOfSubscriptionAdminTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "TestType", publicItem, eperson, subscriptionParameterList).build(); + "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -789,8 +954,8 @@ public void linkedDSpaceObjectOfSubscriptionTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "content", publicItem, eperson, subscriptionParameterList).build(); @@ -812,14 +977,14 @@ public void linkedDSpaceObjectOfSubscriptionUnauthorizedTest() throws Exception context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "content", publicItem, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); - getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/dSpaceObject")) + getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) .andExpect(status().isUnauthorized()); } @@ -828,22 +993,22 @@ public void linkedDSpaceObjectOfSubscriptionForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); List subscriptionParameterList = new ArrayList<>(); SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setName("Parameter"); - subscriptionParameter.setValue("ValueParameter"); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, "content", publicItem, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/dSpaceObject")) + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) .andExpect(status().isForbidden()); } @Test public void linkedDSpaceObjectOfSubscriptionNotFoundTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE + "/dSpaceObject")) + getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + Integer.MAX_VALUE + "/resource")) .andExpect(status().isNotFound()); } From c59438b936afddf96a60c5fce77bde719b6d6252 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 27 Jan 2023 12:10:16 +0100 Subject: [PATCH 200/394] [CST-7756] added validation logic of params --- .../SubscriptionRestRepository.java | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 69ada3e10636..2342b026247b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -178,26 +178,9 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); if (CollectionUtils.isNotEmpty(subscriptionParameterList)) { List subscriptionParameters = new ArrayList<>(); - for (SubscriptionParameterRest subscriptionParameterRest : subscriptionParameterList) { - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - var name = subscriptionParameterRest.getName(); - var value = subscriptionParameterRest.getValue(); - if (!nameAndValueAreSupported(name, value)) { - throw new UnprocessableEntityException("Provided SubscriptionParameter name:" + name + - " or value: " + value + " is not supported!"); - } - subscriptionParameter.setName(name); - subscriptionParameter.setValue(value); - subscriptionParameters.add(subscriptionParameter); - } - - var type = subscriptionRest.getSubscriptionType(); - if (!subscriptionEmailNotificationService.getSupportedSubscriptionTypes().contains(type)) { - throw new UnprocessableEntityException("Provided subscriptionType:" + type + " is not supported!" + - " Must be one of: " + subscriptionEmailNotificationService.getSupportedSubscriptionTypes()); - } - - subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, type); + validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); + subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, + subscriptionRest.getSubscriptionType()); } context.commit(); return converter.toRest(subscription, utils.obtainProjection()); @@ -208,6 +191,29 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, } } + private void validateParameters(SubscriptionRest subscriptionRest, + List subscriptionParameterList, + List subscriptionParameters) { + for (SubscriptionParameterRest subscriptionParameterRest : subscriptionParameterList) { + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + var name = subscriptionParameterRest.getName(); + var value = subscriptionParameterRest.getValue(); + if (!nameAndValueAreSupported(name, value)) { + throw new UnprocessableEntityException("Provided SubscriptionParameter name:" + name + + " or value: " + value + " is not supported!"); + } + subscriptionParameter.setName(name); + subscriptionParameter.setValue(value); + subscriptionParameters.add(subscriptionParameter); + } + + var type = subscriptionRest.getSubscriptionType(); + if (!subscriptionEmailNotificationService.getSupportedSubscriptionTypes().contains(type)) { + throw new UnprocessableEntityException("Provided subscriptionType:" + type + " is not supported!" + + " Must be one of: " + subscriptionEmailNotificationService.getSupportedSubscriptionTypes()); + } + } + private boolean nameAndValueAreSupported(String name, String value) { return subscriptionEmailNotificationService.isSupportedSubscriptionParameterName(name) && subscriptionEmailNotificationService.getSubscriptionParameterValuesByName(name).contains(value); @@ -238,22 +244,18 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri if (Objects.isNull(ePerson)) { throw new ResourceNotFoundException("There is not ePerson with uuid:" + epersonId); } + DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); if (Objects.isNull(dSpaceObject)) { throw new ResourceNotFoundException("There is not DSpaceObject with uuid:" + dsoId); } if (id.equals(subscription.getID())) { - List subscriptionParameterList = new ArrayList<>(); - for (SubscriptionParameterRest subscriptionParamRest : subscriptionRest.getSubscriptionParameterList()) { - SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); - subscriptionParameter.setSubscription(subscription); - subscriptionParameter.setValue(subscriptionParamRest.getValue()); - subscriptionParameter.setName(subscriptionParamRest.getName()); - subscriptionParameterList.add(subscriptionParameter); - } + List subscriptionParameters = new ArrayList<>(); + List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); + validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); subscription = subscribeService.updateSubscription(context, id, ePerson, dSpaceObject, - subscriptionParameterList, subscriptionRest.getSubscriptionType()); + subscriptionParameters, subscriptionRest.getSubscriptionType()); context.commit(); return converter.toRest(subscription, utils.obtainProjection()); } else { From f5c1690e0b0163da9996b1119a3edbec7312c6aa Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 27 Jan 2023 14:53:53 +0100 Subject: [PATCH 201/394] [CST-7756] renamed some methods & class --- .../org/dspace/eperson/SubscribeCLITool.java | 8 ++--- .../dspace/eperson/SubscribeServiceImpl.java | 4 +-- .../java/org/dspace/eperson/Subscription.java | 6 ++-- .../subscriptions/ContentGenerator.java | 2 +- .../SubscriptionEmailNotification.java | 2 +- ...scriptionEmailNotificationServiceImpl.java | 30 +++++++++---------- .../{ItemsUpdates.java => ItemUpdates.java} | 6 ++-- ...ubscriptionDSpaceObjectLinkRepository.java | 10 +++---- .../SubscriptionEPersonLinkRepository.java | 2 +- ...criptionRestPermissionEvaluatorPlugin.java | 2 +- .../api/subscriptions_email_configuration.xml | 2 +- 11 files changed, 37 insertions(+), 37 deletions(-) rename dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/{ItemsUpdates.java => ItemUpdates.java} (97%) diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java index 42841ea36b9f..dd0bd8287bd3 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java @@ -104,12 +104,12 @@ public static void processDaily(Context context, boolean test) throws SQLExcepti // Go through the list collating subscriptions for each e-person for (Subscription subscription : subscriptions) { - if (!(subscription.getdSpaceObject() != null && subscription.getdSpaceObject() instanceof Collection)) { + if (!(subscription.getDSpaceObject() != null && subscription.getDSpaceObject() instanceof Collection)) { continue; } // Does this row relate to the same e-person as the last? if ((currentEPerson == null) - || (!subscription.getePerson().getID().equals(currentEPerson + || (!subscription.getEPerson().getID().equals(currentEPerson .getID()))) { // New e-person. Send mail for previous e-person if (currentEPerson != null) { @@ -123,11 +123,11 @@ public static void processDaily(Context context, boolean test) throws SQLExcepti } } - currentEPerson = subscription.getePerson(); + currentEPerson = subscription.getEPerson(); collections = new ArrayList<>(); } - collections.add((Collection) subscription.getdSpaceObject()); + collections.add((Collection) subscription.getDSpaceObject()); } // Process the last person diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index b54e7c5ec674..b92f1138d891 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -70,7 +70,7 @@ public Subscription subscribe(Context context, EPerson eperson, Subscription newSubscription = subscriptionDAO.create(context, new Subscription()); subscriptionParameterList.forEach(subscriptionParameter -> newSubscription.addParameter(subscriptionParameter)); - newSubscription.setePerson(eperson); + newSubscription.setEPerson(eperson); newSubscription.setdSpaceObject(dSpaceObject); newSubscription.setSubscriptionType(type); return newSubscription; @@ -156,7 +156,7 @@ public Subscription updateSubscription(Context context, Integer id, EPerson eper subscriptionDB.setSubscriptionType(type); subscriptionDB.setdSpaceObject(dSpaceObject); subscriptionParameterList.forEach(x -> subscriptionDB.addParameter(x)); - subscriptionDB.setePerson(eperson); + subscriptionDB.setEPerson(eperson); subscriptionDAO.save(context, subscriptionDB); return subscriptionDB; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index c706a1db60ad..7ca19c6020e6 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -71,7 +71,7 @@ public Integer getID() { return id; } - public DSpaceObject getdSpaceObject() { + public DSpaceObject getDSpaceObject() { return this.dSpaceObject; } @@ -79,11 +79,11 @@ void setDSpaceObject(DSpaceObject dSpaceObject) { this.dSpaceObject = dSpaceObject; } - public EPerson getePerson() { + public EPerson getEPerson() { return ePerson; } - public void setePerson(EPerson ePerson) { + public void setEPerson(EPerson ePerson) { this.ePerson = ePerson; } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index a01a6e33a45e..e017cbe2f07c 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -34,7 +34,7 @@ /** * Implementation class of SubscriptionGenerator * which will handle the logic of sending the emails - * in case of content subscriptions + * in case of 'content' subscriptionType */ @SuppressWarnings("rawtypes") public class ContentGenerator implements SubscriptionGenerator { diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java index 04c9429c6726..7cfa9bd33451 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java @@ -41,7 +41,7 @@ public SubscriptionEmailNotificationConfiguration @Override public void setup() throws ParseException { this.subscriptionEmailNotificationService = new DSpace().getServiceManager().getServiceByName( - SubscriptionEmailNotificationService.class.getName(), SubscriptionEmailNotificationService.class); + SubscriptionEmailNotificationServiceImpl.class.getName(), SubscriptionEmailNotificationServiceImpl.class); } @Override diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java index 8bf5af06c743..d9937143cb24 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -77,12 +77,12 @@ public SubscriptionEmailNotificationServiceImpl(SubscribeService subscribeServic } @SuppressWarnings("unchecked") - public void perform(Context context, DSpaceRunnableHandler handler, String type, String frequency) { + public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency) { try { - List subscriptions = findAllSubscriptionsBySubscriptionTypeAndFrequency(context, type, - frequency); - // Here is verified if type is "content" Or "statistics" as them are configured - if (subscriptionType2generators.keySet().contains(type)) { + List subscriptions = + findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, frequency); + // Here is verified if SubscriptionType is "content" Or "statistics" as them are configured + if (subscriptionType2generators.keySet().contains(subscriptionType)) { // the list of the person who has subscribed int iterator = 0; for (Subscription subscription : subscriptions) { @@ -97,13 +97,13 @@ public void perform(Context context, DSpaceRunnableHandler handler, String type, items.addAll(contentUpdates.get(Item.class.getSimpleName().toLowerCase(Locale.ROOT)) .findUpdates(context, dSpaceObject, frequency)); } - var ePerson = subscription.getePerson(); + var ePerson = subscription.getEPerson(); if (iterator < subscriptions.size() - 1) { - if (ePerson.equals(subscriptions.get(iterator + 1).getePerson())) { + if (ePerson.equals(subscriptions.get(iterator + 1).getEPerson())) { iterator++; continue; } else { - subscriptionType2generators.get(type) + subscriptionType2generators.get(subscriptionType) .notifyForSubscriptions(context, ePerson, communities, collections, items); communities.clear(); collections.clear(); @@ -111,14 +111,14 @@ public void perform(Context context, DSpaceRunnableHandler handler, String type, } } else { //in the end of the iteration - subscriptionType2generators.get(type) + subscriptionType2generators.get(subscriptionType) .notifyForSubscriptions(context, ePerson, communities, collections, items); } iterator++; } } else { - throw new IllegalArgumentException("Currently this type:" + type + - " of subscription is not supported!"); + throw new IllegalArgumentException("Currently this SubscriptionType:" + subscriptionType + + " is not supported!"); } } catch (Exception e) { log.error(e.getMessage(), e); @@ -128,9 +128,9 @@ public void perform(Context context, DSpaceRunnableHandler handler, String type, } private DSpaceObject getdSpaceObject(Subscription subscription) { - DSpaceObject dSpaceObject = subscription.getdSpaceObject(); - if (subscription.getdSpaceObject() instanceof HibernateProxy) { - HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); + DSpaceObject dSpaceObject = subscription.getDSpaceObject(); + if (subscription.getDSpaceObject() instanceof HibernateProxy) { + HibernateProxy hibernateProxy = (HibernateProxy) subscription.getDSpaceObject(); LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); dSpaceObject = (DSpaceObject) initializer.getImplementation(); } @@ -143,7 +143,7 @@ private List findAllSubscriptionsBySubscriptionTypeAndFrequency(Co return subscribeService.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, frequency) .stream() - .sorted(Comparator.comparing(s -> s.getePerson().getID())) + .sorted(Comparator.comparing(s -> s.getEPerson().getID())) .collect(Collectors.toList()); } catch (SQLException e) { log.error(e.getMessage(), e); diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemUpdates.java similarity index 97% rename from dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java rename to dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemUpdates.java index de8bd2beb7fc..5c8a290a45f1 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemUpdates.java @@ -46,9 +46,9 @@ * * @author Alba Aliu */ -public class ItemsUpdates implements DSpaceObjectUpdates { +public class ItemUpdates implements DSpaceObjectUpdates { - private final Logger log = LogManager.getLogger(ItemsUpdates.class); + private final Logger log = LogManager.getLogger(ItemUpdates.class); private final CollectionService collectionService; private final CommunityService communityService; @@ -181,7 +181,7 @@ private DiscoverQuery buildBaseQuery(DiscoveryConfiguration discoveryConfigurati return discoverQuery; } - public ItemsUpdates(CollectionService collectionService, CommunityService communityService, ItemService itemService, + public ItemUpdates(CollectionService collectionService, CommunityService communityService, ItemService itemService, DiscoveryConfigurationService searchConfigurationService, SearchService searchService) { this.collectionService = collectionService; this.communityService = communityService; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index 762736d5d0c6..fc0907c4bf0d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -45,12 +45,12 @@ public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, In if (Objects.isNull(subscription)) { throw new ResourceNotFoundException("No such subscription: " + subscriptionId); } - if (subscription.getdSpaceObject() instanceof Item || - subscription.getdSpaceObject() instanceof Community || - subscription.getdSpaceObject() instanceof Collection) { - return converter.toRest(subscription.getdSpaceObject(), projection); + if (subscription.getDSpaceObject() instanceof Item || + subscription.getDSpaceObject() instanceof Community || + subscription.getDSpaceObject() instanceof Collection) { + return converter.toRest(subscription.getDSpaceObject(), projection); } else { - HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject(); + HibernateProxy hibernateProxy = (HibernateProxy) subscription.getDSpaceObject(); LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); return converter.toRest(initializer.getImplementation(), projection); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java index 4857b1f2d858..dcf612e52daa 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionEPersonLinkRepository.java @@ -40,7 +40,7 @@ public EPersonRest getEPerson(@Nullable HttpServletRequest request, Integer subs if (Objects.isNull(subscription)) { throw new ResourceNotFoundException("No such subscription: " + subscriptionId); } - return converter.toRest(subscription.getePerson(), projection); + return converter.toRest(subscription.getEPerson(), projection); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java index b92fed943534..0f637d186a5f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java @@ -74,7 +74,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } Subscription subscription = subscribeService.findById(context, Integer.parseInt(targetId.toString())); - return Objects.nonNull(subscription) ? currentUser.equals(subscription.getePerson()) : true; + return Objects.nonNull(subscription) ? currentUser.equals(subscription.getEPerson()) : true; } catch (SQLException e) { log.error(e.getMessage(), e); } diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index 42d67632720c..597968e34733 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -82,6 +82,6 @@ - + From cb4fd99211c3ee1f04c23ae28290cb8a7f299825 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 27 Jan 2023 18:09:50 +0100 Subject: [PATCH 202/394] [CST-7756] minor fix --- .../impl/CanSubscribeFeature.java | 4 ++-- ...SubscriptionDSpaceObjectLinkRepository.java | 2 +- dspace/config/spring/api/scripts.xml | 2 +- .../api/subscriptions_email_configuration.xml | 18 ++++++------------ 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java index 57b029c75eb7..fb1398448691 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java @@ -26,13 +26,13 @@ import org.springframework.stereotype.Component; /** - * Checks if the given user can subscribe to a dataspace object + * Checks if the given user can subscribe to a DSpace object * * @author Alba Aliu (alba.aliu at atis.al) */ @Component @AuthorizationFeatureDocumentation(name = CanSubscribeFeature.NAME, - description = "Used to verify if the given user can subscribe to a dataspace object") + description = "Used to verify if the given user can subscribe to a DSpace object") public class CanSubscribeFeature implements AuthorizationFeature { public static final String NAME = "canSubscribeDso"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index fc0907c4bf0d..dd9db2aea660 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; /** - * Link repository for "dataSpaceObject" of subscription + * Link repository for "DSpaceObject" of subscription */ @Component(SubscriptionRest.CATEGORY + "." + SubscriptionRest.NAME + "." + SubscriptionRest.DSPACE_OBJECT) public class SubscriptionDSpaceObjectLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index fa7c97eccdc2..19f558dab6d2 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -82,7 +82,7 @@ - + diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index 597968e34733..d03a30ac3552 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -46,7 +46,7 @@ - + @@ -61,27 +61,21 @@ - + - - - - + - - - - + - + - + From 1593e1a867586c602aab5ee6b2affe3530a260d7 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 30 Jan 2023 13:30:01 +0100 Subject: [PATCH 203/394] [CST-7756] refactored CanSubscribeFeatureIT --- .../authorization/CanSubscribeFeatureIT.java | 362 +++++++++--------- 1 file changed, 179 insertions(+), 183 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java index fe43c0a73d8c..10498c273f1a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java @@ -7,8 +7,6 @@ */ package org.dspace.app.rest.authorization; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -17,18 +15,19 @@ import java.sql.SQLException; import java.util.List; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.authorization.impl.CanSubscribeFeature; import org.dspace.app.rest.converter.CollectionConverter; import org.dspace.app.rest.converter.CommunityConverter; import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; @@ -46,6 +45,7 @@ import org.dspace.discovery.SearchServiceException; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -53,19 +53,17 @@ /** * Test of Subscribe Dso Feature implementation. * - * @author Alba Aliu (alba.aliu at 4science.it) + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ public class CanSubscribeFeatureIT extends AbstractControllerIntegrationTest { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CanSubscribeFeatureIT.class); + private static final Logger log = LogManager.getLogger(CanSubscribeFeatureIT.class); @Autowired private ItemConverter itemConverter; @Autowired private CollectionConverter collectionConverter; @Autowired - private Utils utils; - @Autowired private CommunityConverter communityConverter; @Autowired private AuthorizationFeatureService authorizationFeatureService; @@ -86,243 +84,242 @@ public void setUp() throws Exception { .build(); communityAuthorized = CommunityBuilder.createCommunity(context) .withName("communityA") - .withAdminGroup(admin) .build(); collectionAuthorized = CollectionBuilder.createCollection(context, communityAuthorized) .withName("Collection A") - .withAdminGroup(admin) .build(); context.restoreAuthSystemState(); canSubscribeFeature = authorizationFeatureService.find(CanSubscribeFeature.NAME); } @Test - public void testCanSubscribeCommunity() throws Exception { + public void canSubscribeCommunityAndCollectionTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson ePersonAuthorized = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); - String token = getAuthToken(ePersonAuthorized.getEmail(), password); CommunityRest comRest = communityConverter.convert(parentCommunity, DefaultProjection.DEFAULT); - String comUri = utils.linkToSingleResource(comRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").exists()) - .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); - } + CollectionRest colRest = collectionConverter.convert(collectionAuthorized, DefaultProjection.DEFAULT); - @Test - public void anonymousCanSubscribeCommunityTest() throws Exception { - context.turnOffAuthorisationSystem(); - CommunityRest comRest = communityConverter.convert(parentCommunity, DefaultProjection.DEFAULT); - String comUri = utils.linkToSingleResource(comRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient().perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").doesNotExist()) - .andExpect(jsonPath("$.page.totalElements", is(0))); - } + // define authorizations that we know must exists + Authorization epersonToCommunity = new Authorization(eperson, canSubscribeFeature, comRest); + Authorization adminToCommunity = new Authorization(admin, canSubscribeFeature, comRest); + Authorization epersonToCollection = new Authorization(eperson, canSubscribeFeature, colRest); + Authorization adminToCollection = new Authorization(admin, canSubscribeFeature, colRest); - @Test - public void anonymousCanSubscribeCollectionTest() throws Exception { - context.turnOffAuthorisationSystem(); - Collection collectionWithReadPermission = CollectionBuilder.createCollection(context, communityAuthorized) - .build(); - context.restoreAuthSystemState(); - CollectionRest collectionRest = collectionConverter.convert(collectionWithReadPermission, Projection.DEFAULT); - String comUri = utils.linkToSingleResource(collectionRest, "self").getHref(); + // define authorization that we know not exists + Authorization anonymousToCommunity = new Authorization(null, canSubscribeFeature, comRest); + Authorization anonymousToCollection = new Authorization(null, canSubscribeFeature, colRest); context.restoreAuthSystemState(); - getClient().perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").doesNotExist()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToCommunity.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToCommunity)))); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToCommunity.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToCommunity)))); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToCollection.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToCollection)))); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToCollection.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToCollection)))); + + getClient().perform(get("/api/authz/authorizations/" + anonymousToCommunity.getID())) + .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymousToCollection.getID())) + .andExpect(status().isNotFound()); } @Test - public void anonymousCanSubscribeItemTest() throws Exception { + public void canSubscribeItemTest() throws Exception { context.turnOffAuthorisationSystem(); Item item = ItemBuilder.createItem(context, collectionAuthorized) .withTitle("Test item") .build(); - context.restoreAuthSystemState(); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); - String comUri = utils.linkToSingleResource(itemRest, "self").getHref(); + + // define authorizations that we know must exists + Authorization epersonToItem = new Authorization(eperson, canSubscribeFeature, itemRest); + Authorization adminToItem = new Authorization(admin, canSubscribeFeature, itemRest); + + // define authorization that we know not exists + Authorization anonymousToItem = new Authorization(null, canSubscribeFeature, itemRest); + context.restoreAuthSystemState(); - getClient().perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").doesNotExist()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToItem)))); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToItem)))); + + getClient().perform(get("/api/authz/authorizations/" + anonymousToItem.getID())) + .andExpect(status().isNotFound()); } @Test - public void testCanNotSubscribeItem() throws Exception { + public void canNotSubscribeItemTest() throws Exception { context.turnOffAuthorisationSystem(); EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); // the user to be tested is not part of the group with read permission Group groupWithReadPermission = GroupBuilder.createGroup(context) - .withName("Group A") - .addMember(admin) - .build(); + .withName("Group A") + .addMember(eperson) + .build(); Item item = ItemBuilder.createItem(context, collectionAuthorized) - .withTitle("Test item") - .build(); + .withTitle("Test item") + .build(); + cleanUpPermissions(resourcePolicyService.find(context, item)); setPermissions(item, groupWithReadPermission, Constants.READ); - item.setSubmitter(eperson); - context.restoreAuthSystemState(); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); - String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(itemRest, "self").getHref(); + + // define authorizations that we know must exists + Authorization epersonToItem = new Authorization(eperson, canSubscribeFeature, itemRest); + Authorization adminToItem = new Authorization(admin, canSubscribeFeature, itemRest); + + // define authorization that we know not exists + Authorization ePersonNotSubscribePermissionToItem = new Authorization(ePersonNotSubscribePermission, + canSubscribeFeature, itemRest); + context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page").exists()) - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.totalPages", is(0))) - .andExpect(jsonPath("$.page.number", is(0))) - // default value of page size - .andExpect(jsonPath("$.page.size", is(20))); + + String token1 = getAuthToken(eperson.getEmail(), password); + String token2 = getAuthToken(admin.getEmail(), password); + String token3 = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); + + getClient(token1).perform(get("/api/authz/authorizations/" + epersonToItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToItem)))); + + getClient(token2).perform(get("/api/authz/authorizations/" + adminToItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToItem)))); + + getClient(token3).perform(get("/api/authz/authorizations/" + ePersonNotSubscribePermissionToItem.getID())) + .andExpect(status().isNotFound()); } @Test - public void testCanNotSubscribeCollection() throws Exception { + public void canNotSubscribeCollectionTest() throws Exception { context.turnOffAuthorisationSystem(); - Collection collectionWithReadPermission = CollectionBuilder.createCollection(context, communityAuthorized) - .withAdminGroup(admin).build(); + EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); + .withCanLogin(true) + .withPassword(password) + .withEmail("test@email.it") + .build(); + // the user to be tested is not part of the group with read permission Group groupWithReadPermission = GroupBuilder.createGroup(context) - .withName("Group A") - .addMember(eperson) - .build(); - cleanUpPermissions(resourcePolicyService.find(context, collectionWithReadPermission)); - setPermissions(collectionWithReadPermission, groupWithReadPermission, Constants.READ); + .withName("Group A") + .addMember(eperson) + .build(); + + cleanUpPermissions(resourcePolicyService.find(context, collectionAuthorized)); + setPermissions(collectionAuthorized, groupWithReadPermission, Constants.READ); + + CollectionRest collectionRest = collectionConverter.convert(collectionAuthorized, Projection.DEFAULT); + + // define authorizations that we know must exists + Authorization epersonToCollection = new Authorization(eperson, canSubscribeFeature, collectionRest); + Authorization adminToCollection = new Authorization(admin, canSubscribeFeature, collectionRest); + + // define authorization that we know not exists + Authorization ePersonNotSubscribePermissionToColl = new Authorization(ePersonNotSubscribePermission, + canSubscribeFeature, collectionRest); + context.restoreAuthSystemState(); - CollectionRest collectionRest = collectionConverter.convert(collectionWithReadPermission, Projection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(collectionRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page").exists()) - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.totalPages", is(0))) - .andExpect(jsonPath("$.page.number", is(0))) - // default value of page size - .andExpect(jsonPath("$.page.size", is(20))); + + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToCollection.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToCollection)))); + + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToCollection.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToCollection)))); + + getClient(token).perform(get("/api/authz/authorizations/" + ePersonNotSubscribePermissionToColl.getID())) + .andExpect(status().isNotFound()); } @Test - public void testCanNotSubscribeCommunity() throws Exception { + public void canNotSubscribeCommunityTest() throws Exception { context.turnOffAuthorisationSystem(); - Community communityWithReadPermissions = CommunityBuilder.createCommunity(context).build(); + EPerson ePersonNotSubscribePermission = EPersonBuilder.createEPerson(context) .withCanLogin(true) .withPassword(password) .withEmail("test@email.it") .build(); + // the user to be tested is not part of the group with read permission Group groupWithReadPermission = GroupBuilder.createGroup(context) .withName("Group A") - .addMember(admin) .addMember(eperson) .build(); - cleanUpPermissions(resourcePolicyService.find(context, communityWithReadPermissions)); - setPermissions(communityWithReadPermissions, groupWithReadPermission, Constants.READ); + + cleanUpPermissions(resourcePolicyService.find(context, communityAuthorized)); + setPermissions(communityAuthorized, groupWithReadPermission, Constants.READ); + + CommunityRest communityRest = communityConverter.convert(communityAuthorized, Projection.DEFAULT); + + // define authorizations that we know must exists + Authorization epersonToComm = new Authorization(eperson, canSubscribeFeature, communityRest); + Authorization adminToComm = new Authorization(admin, canSubscribeFeature, communityRest); + + // define authorization that we know not exists + Authorization ePersonNotSubscribePermissionToComm = new Authorization(ePersonNotSubscribePermission, + canSubscribeFeature, communityRest); + context.restoreAuthSystemState(); - CommunityRest communityRest = communityConverter.convert(communityWithReadPermissions, Projection.DEFAULT); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + String tokenEPerson = getAuthToken(eperson.getEmail(), password); String token = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(communityRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page").exists()) - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.totalPages", is(0))) - .andExpect(jsonPath("$.page.number", is(0))) - // default value of page size - .andExpect(jsonPath("$.page.size", is(20))); - } - @Test - public void testCanSubscribeCollection() throws Exception { - context.turnOffAuthorisationSystem(); - Collection collectionWithReadPermission = CollectionBuilder.createCollection(context, communityAuthorized) - .withAdminGroup(admin).build(); - EPerson ePersonSubscribePermission = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); - context.restoreAuthSystemState(); - CollectionRest collectionRest = collectionConverter.convert(collectionWithReadPermission, Projection.DEFAULT); - String token = getAuthToken(ePersonSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(collectionRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").exists()) - .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); - } + getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToComm.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(epersonToComm)))); - @Test - public void testCanSubscribeItem() throws Exception { - context.turnOffAuthorisationSystem(); - EPerson ePersonSubscribePermission = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withPassword(password) - .withEmail("test@email.it") - .build(); - Item item = ItemBuilder.createItem(context, collectionAuthorized) - .withTitle("Test item") - .build(); - context.restoreAuthSystemState(); - ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); - String token = getAuthToken(ePersonSubscribePermission.getEmail(), password); - String comUri = utils.linkToSingleResource(itemRest, "self").getHref(); - context.restoreAuthSystemState(); - getClient(token).perform(get("/api/authz/authorizations/search/object") - .param("uri", comUri) - .param("feature", canSubscribeFeature.getName()) - .param("embed", "feature")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded").exists()) - .andExpect(jsonPath("$.page.totalElements", greaterThan(0))); + getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToComm.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + AuthorizationMatcher.matchAuthorization(adminToComm)))); + + getClient(token).perform(get("/api/authz/authorizations/" + ePersonNotSubscribePermissionToComm.getID())) + .andExpect(status().isNotFound()); } private void setPermissions(DSpaceObject dSpaceObject, Group group, Integer permissions) { @@ -342,7 +339,6 @@ private void cleanUpPermissions(List resourcePolicies) { for (ResourcePolicy resourcePolicy : resourcePolicies) { ResourcePolicyBuilder.delete(resourcePolicy.getID()); } - } catch (SQLException | SearchServiceException | IOException sqlException) { log.error(sqlException.getMessage()); } From 659382fa8b71eb77a32d6877263fc9b4cd801d4b Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 31 Jan 2023 12:27:39 +0100 Subject: [PATCH 204/394] 97425: Implement feedback --- .../org/dspace/alerts/AllowSessionsEnum.java | 9 +- .../alerts/SystemWideAlertServiceImpl.java | 31 +- .../service/SystemWideAlertService.java | 6 +- ...> V7.5_2022.12.15__system_wide_alerts.sql} | 0 ...> V7.5_2022.12.15__system_wide_alerts.sql} | 0 ...> V7.5_2022.12.15__system_wide_alerts.sql} | 0 .../alerts/SystemWideAlertServiceTest.java | 202 +++++++++++ .../org/dspace/builder/AbstractBuilder.java | 5 + .../builder/SystemWideAlertBuilder.java | 112 ++++++ .../SystemWideAlertRestRepository.java | 50 ++- .../rest/SystemWideAlertRestRepositoryIT.java | 321 ++++++++++++++---- 11 files changed, 663 insertions(+), 73 deletions(-) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/{V7.4_2022.12.15__system_wide_alerts.sql => V7.5_2022.12.15__system_wide_alerts.sql} (100%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/{V7.4_2022.12.15__system_wide_alerts.sql => V7.5_2022.12.15__system_wide_alerts.sql} (100%) rename dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/{V7.4_2022.12.15__system_wide_alerts.sql => V7.5_2022.12.15__system_wide_alerts.sql} (100%) create mode 100644 dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java create mode 100644 dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java diff --git a/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java index 0c8b3f72d283..b43b350f7c2f 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java +++ b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java @@ -8,7 +8,14 @@ package org.dspace.alerts; /** - * Enum representing the options for allowing sessions + * Enum representing the options for allowing sessions: + * ALLOW_ALL_SESSIONS - Will allow all users to log in and continue their sessions + * ALLOW_CURRENT_SESSIONS_ONLY - Will prevent non admin users from logging in, however logged-in users + * will remain logged in + * ALLOW_ADMIN_SESSIONS_ONLY - Only admin users can log in, non admin sessions will be interrupted + * + * NOTE: This functionality can be stored in the database, but no support is present right now to interrupt and prevent + * sessions. */ public enum AllowSessionsEnum { ALLOW_ALL_SESSIONS(0), diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java index cc59289e42b2..d0e98139d2fe 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java @@ -20,7 +20,6 @@ import org.dspace.core.Context; import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; -import org.dspace.eperson.service.EPersonService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -37,12 +36,15 @@ public class SystemWideAlertServiceImpl implements SystemWideAlertService { @Autowired private AuthorizeService authorizeService; - @Autowired - private EPersonService ePersonService; - + @Override public SystemWideAlert create(final Context context, final String message, final AllowSessionsEnum allowSessionsType, - final Date countdownTo, final boolean active) throws SQLException { + final Date countdownTo, final boolean active) throws SQLException, + AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can create a system-wide alert"); + } SystemWideAlert systemWideAlert = new SystemWideAlert(); systemWideAlert.setMessage(message); systemWideAlert.setAllowSessions(allowSessionsType); @@ -59,36 +61,52 @@ public SystemWideAlert create(final Context context, final String message, return createdAlert; } + @Override public SystemWideAlert find(final Context context, final int alertId) throws SQLException { return systemWideAlertDAO.findByID(context, SystemWideAlert.class, alertId); } + @Override public List findAll(final Context context) throws SQLException { return systemWideAlertDAO.findAll(context, SystemWideAlert.class); } + @Override public List findAll(final Context context, final int limit, final int offset) throws SQLException { return systemWideAlertDAO.findAll(context, limit, offset); } + @Override public List findAllActive(final Context context, final int limit, final int offset) throws SQLException { return systemWideAlertDAO.findAllActive(context, limit, offset); } + @Override public void delete(final Context context, final SystemWideAlert systemWideAlert) throws SQLException, IOException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can create a system-wide alert"); + } systemWideAlertDAO.delete(context, systemWideAlert); log.info(LogHelper.getHeader(context, "system_wide_alert_create", "System Wide Alert with ID " + systemWideAlert.getID() + " has been deleted")); } - public void update(final Context context, final SystemWideAlert systemWideAlert) throws SQLException { + @Override + public void update(final Context context, final SystemWideAlert systemWideAlert) + throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can create a system-wide alert"); + } systemWideAlertDAO.save(context, systemWideAlert); } + @Override public boolean canNonAdminUserLogin(Context context) throws SQLException { List active = findAllActive(context, 1, 0); if (active == null || active.isEmpty()) { @@ -97,6 +115,7 @@ public boolean canNonAdminUserLogin(Context context) throws SQLException { return active.get(0).getAllowSessions() == AllowSessionsEnum.ALLOW_ALL_SESSIONS.getValue(); } + @Override public boolean canUserMaintainSession(Context context, EPerson ePerson) throws SQLException { if (authorizeService.isAdmin(context, ePerson)) { return true; diff --git a/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java b/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java index 85b6ad304c95..cf231308849d 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java +++ b/dspace-api/src/main/java/org/dspace/alerts/service/SystemWideAlertService.java @@ -19,7 +19,7 @@ import org.dspace.eperson.EPerson; /** - * An interface for the ProcessService with methods regarding the SystemWideAlert workload + * An interface for the SystemWideAlertService with methods regarding the SystemWideAlert workload */ public interface SystemWideAlertService { @@ -36,7 +36,7 @@ public interface SystemWideAlertService { */ SystemWideAlert create(Context context, String message, AllowSessionsEnum allowSessionsType, Date countdownTo, boolean active - ) throws SQLException; + ) throws SQLException, AuthorizeException; /** * This method will retrieve a SystemWideAlert object from the Database with the given ID @@ -96,7 +96,7 @@ void delete(Context context, SystemWideAlert systemWideAlert) * @param systemWideAlert The SystemWideAlert object to be updated * @throws SQLException If something goes wrong */ - void update(Context context, SystemWideAlert systemWideAlert) throws SQLException; + void update(Context context, SystemWideAlert systemWideAlert) throws SQLException, AuthorizeException; /** diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.4_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.15__system_wide_alerts.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.4_2022.12.15__system_wide_alerts.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.15__system_wide_alerts.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.4_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.4_2022.12.15__system_wide_alerts.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.4_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.15__system_wide_alerts.sql similarity index 100% rename from dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.4_2022.12.15__system_wide_alerts.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.15__system_wide_alerts.sql diff --git a/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java b/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java new file mode 100644 index 000000000000..7355037ff1d3 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java @@ -0,0 +1,202 @@ +/** + * 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.alerts; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.dspace.alerts.dao.SystemWideAlertDAO; +import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class SystemWideAlertServiceTest { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SystemWideAlertService.class); + + @InjectMocks + private SystemWideAlertServiceImpl systemWideAlertService; + + @Mock + private SystemWideAlertDAO systemWideAlertDAO; + + @Mock + private AuthorizeService authorizeService; + + @Mock + private Context context; + + @Mock + private SystemWideAlert systemWideAlert; + + @Mock + private EPerson eperson; + + + @Test + public void testCreate() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context)).thenReturn(true); + + // Declare objects utilized in unit test + SystemWideAlert systemWideAlert = new SystemWideAlert(); + systemWideAlert.setMessage("Test message"); + systemWideAlert.setAllowSessions(AllowSessionsEnum.ALLOW_ALL_SESSIONS); + systemWideAlert.setCountdownTo(null); + systemWideAlert.setActive(true); + + // Mock DAO to return our defined SystemWideAlert + when(systemWideAlertDAO.create(any(), any())).thenReturn(systemWideAlert); + + // The newly created SystemWideAlert's message should match our mocked SystemWideAlert's message + SystemWideAlert result = systemWideAlertService.create(context, "Test message", + AllowSessionsEnum.ALLOW_ALL_SESSIONS, null, true); + assertEquals("TestCreate 0", systemWideAlert.getMessage(), result.getMessage()); + // The newly created SystemWideAlert should match our mocked SystemWideAlert + assertEquals("TestCreate 1", systemWideAlert, result); + } + + + @Test + public void testFindAll() throws Exception { + // Declare objects utilized in unit test + List systemWideAlertList = new ArrayList<>(); + + // The SystemWideAlert(s) reported from our mocked state should match our systemWideAlertList + assertEquals("TestFindAll 0", systemWideAlertList, systemWideAlertService.findAll(context)); + } + + @Test + public void testFind() throws Exception { + // Mock DAO to return our mocked SystemWideAlert + when(systemWideAlertService.find(context, 0)).thenReturn(systemWideAlert); + + // The SystemWideAlert reported from our ID should match our mocked SystemWideAlert + assertEquals("TestFind 0", systemWideAlert, systemWideAlertService.find(context, 0)); + } + + @Test + public void testFindAllActive() throws Exception { + // Declare objects utilized in unit test + List systemWideAlertList = new ArrayList<>(); + + // The SystemWideAlert(s) reported from our mocked state should match our systemWideAlertList + assertEquals("TestFindAllActive 0", systemWideAlertList, systemWideAlertService.findAllActive(context, 10, 0)); + } + + + @Test + public void testUpdate() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context)).thenReturn(true); + + // Invoke impl of method update() + systemWideAlertService.update(context, systemWideAlert); + + // Verify systemWideAlertDAO.save was invoked twice to confirm proper invocation of both impls of update() + Mockito.verify(systemWideAlertDAO, times(1)).save(context, systemWideAlert); + } + + @Test + public void testDelete() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context)).thenReturn(true); + + // Invoke method delete() + systemWideAlertService.delete(context, systemWideAlert); + + // Verify systemWideAlertDAO.delete() ran once to confirm proper invocation of delete() + Mockito.verify(systemWideAlertDAO, times(1)).delete(context, systemWideAlert); + } + + @Test + public void canNonAdminUserLoginTrueTest() throws Exception { + // Mock the alert state + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ALL_SESSIONS.getValue()); + + // Mock DAO to return our defined systemWideAlertList + List systemWideAlertList = new ArrayList<>(); + systemWideAlertList.add(systemWideAlert); + when(systemWideAlertDAO.findAllActive(context, 1, 0)).thenReturn(systemWideAlertList); + + // Assert the non admin users can log in + assertTrue("CanNonAdminUserLogin 0", systemWideAlertService.canNonAdminUserLogin(context)); + } + + @Test + public void canNonAdminUserLoginFalseTest() throws Exception { + // Mock the alert state + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY.getValue()); + + // Mock DAO to return our defined systemWideAlertList + List systemWideAlertList = new ArrayList<>(); + systemWideAlertList.add(systemWideAlert); + when(systemWideAlertDAO.findAllActive(context, 1, 0)).thenReturn(systemWideAlertList); + + // Assert the non admin users can log in + assertFalse("CanNonAdminUserLogin 1", systemWideAlertService.canNonAdminUserLogin(context)); + } + + @Test + public void canUserMaintainSessionAdminTest() throws Exception { + // Assert the admin user can log in + assertTrue("CanUserMaintainSession 0", systemWideAlertService.canNonAdminUserLogin(context)); + } + @Test + public void canUserMaintainSessionTrueTest() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context, eperson)).thenReturn(false); + + // Mock the alert state + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); + + // Mock DAO to return our defined systemWideAlertList + List systemWideAlertList = new ArrayList<>(); + systemWideAlertList.add(systemWideAlert); + when(systemWideAlertDAO.findAllActive(context, 1, 0)).thenReturn(systemWideAlertList); + + // Assert the non admin users can main session + assertTrue("CanUserMaintainSession 1", systemWideAlertService.canUserMaintainSession(context, eperson)); + } + + @Test + public void canUserMaintainSessionFalseTest() throws Exception { + // Mock admin state + when(authorizeService.isAdmin(context, eperson)).thenReturn(false); + + // Mock the alert state + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY.getValue()); + + // Mock DAO to return our defined systemWideAlertList + List systemWideAlertList = new ArrayList<>(); + systemWideAlertList.add(systemWideAlert); + when(systemWideAlertDAO.findAllActive(context, 1, 0)).thenReturn(systemWideAlertList); + + // Assert the non admin users cannot main session + assertFalse("CanUserMaintainSession 2", systemWideAlertService.canUserMaintainSession(context, eperson)); + } + + + +} diff --git a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java index 3306ced8f434..05bb659ac5b0 100644 --- a/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/AbstractBuilder.java @@ -13,6 +13,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.alerts.service.SystemWideAlertService; import org.dspace.app.requestitem.factory.RequestItemServiceFactory; import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.authorize.AuthorizeException; @@ -102,6 +103,7 @@ public abstract class AbstractBuilder { static OrcidHistoryService orcidHistoryService; static OrcidQueueService orcidQueueService; static OrcidTokenService orcidTokenService; + static SystemWideAlertService systemWideAlertService; protected Context context; @@ -161,6 +163,8 @@ public static void init() { orcidHistoryService = OrcidServiceFactory.getInstance().getOrcidHistoryService(); orcidQueueService = OrcidServiceFactory.getInstance().getOrcidQueueService(); orcidTokenService = OrcidServiceFactory.getInstance().getOrcidTokenService(); + systemWideAlertService = DSpaceServicesFactory.getInstance().getServiceManager() + .getServicesByType(SystemWideAlertService.class).get(0); } @@ -194,6 +198,7 @@ public static void destroy() { requestItemService = null; versioningService = null; orcidTokenService = null; + systemWideAlertService = null; } diff --git a/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java new file mode 100644 index 000000000000..dbc902b97a0c --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java @@ -0,0 +1,112 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.builder; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Date; + +import org.dspace.alerts.AllowSessionsEnum; +import org.dspace.alerts.SystemWideAlert; +import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.scripts.Process; + +public class SystemWideAlertBuilder extends AbstractBuilder { + + private SystemWideAlert systemWideAlert; + + protected SystemWideAlertBuilder(Context context) { + super(context); + } + + public static SystemWideAlertBuilder createSystemWideAlert(Context context, String message) + throws SQLException, AuthorizeException { + SystemWideAlertBuilder systemWideAlertBuilder = new SystemWideAlertBuilder(context); + return systemWideAlertBuilder.create(context, message, AllowSessionsEnum.ALLOW_ALL_SESSIONS, null, false); + } + + private SystemWideAlertBuilder create(Context context, String message, AllowSessionsEnum allowSessionsType, + Date countdownTo, boolean active) + throws SQLException, AuthorizeException { + this.context = context; + this.systemWideAlert = systemWideAlertService.create(context, message, allowSessionsType, countdownTo, active); + return this; + } + + public SystemWideAlertBuilder withAllowSessions(AllowSessionsEnum allowSessionsType) { + systemWideAlert.setAllowSessions(allowSessionsType); + return this; + } + + public SystemWideAlertBuilder withCountdownDate(Date countdownTo) { + systemWideAlert.setCountdownTo(countdownTo); + return this; + } + + public SystemWideAlertBuilder isActive(boolean isActive) { + systemWideAlert.setActive(isActive); + return this; + } + + @Override + public void cleanup() throws Exception { + try (Context c = new Context()) { + c.setDispatcher("noindex"); + c.turnOffAuthorisationSystem(); + // Ensure object and any related objects are reloaded before checking to see what needs cleanup + systemWideAlert = c.reloadEntity(systemWideAlert); + if (systemWideAlert != null) { + delete(c, systemWideAlert); + } + c.complete(); + indexingService.commit(); + } + } + + @Override + public SystemWideAlert build() { + try { + systemWideAlertService.update(context, systemWideAlert); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + return null; + } + return systemWideAlert; + } + + + @Override + protected SystemWideAlertService getService() { + return systemWideAlertService; + } + + public void delete(Context c, SystemWideAlert alert) throws Exception { + if (alert != null) { + getService().delete(c, alert); + } + } + + public static void deleteProcess(Integer integer) throws SQLException, IOException { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Process process = processService.find(c, integer); + if (process != null) { + try { + processService.delete(c, process); + } catch (AuthorizeException e) { + // cannot occur, just wrap it to make the compiler happy + throw new RuntimeException(e); + } + } + c.complete(); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java index bdca9eaf6972..6b2e28ebee40 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java @@ -23,10 +23,12 @@ import org.dspace.alerts.AllowSessionsEnum; import org.dspace.alerts.SystemWideAlert; import org.dspace.alerts.service.SystemWideAlertService; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.SystemWideAlertRest; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -46,6 +48,9 @@ public class SystemWideAlertRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { try { List systemWideAlerts = systemWideAlertService.findAll(context, pageable.getPageSize(), @@ -118,12 +138,13 @@ protected SystemWideAlertRest put(Context context, HttpServletRequest request, S /** * Helper method to create a system-wide alert and deny creation when one already exists - * @param context The database context + * + * @param context The database context * @return the created system-wide alert * @throws SQLException */ private SystemWideAlert createSystemWideAlert(Context context) - throws SQLException { + throws SQLException, AuthorizeException { List all = systemWideAlertService.findAll(context); if (!all.isEmpty()) { throw new DSpaceBadRequestException("A system wide alert already exists, no new value can be created. " + @@ -156,6 +177,29 @@ private SystemWideAlert createSystemWideAlert(Context context) return systemWideAlert; } + /** + * Search method to retrieve all active system-wide alerts + * + * @param pageable The page object + * @return all active system-wide alerts for the provided page + */ + @PreAuthorize("permitAll()") + @SearchRestMethod(name = "active") + public Page findAllActive(Pageable pageable) { + Context context = obtainContext(); + try { + List systemWideAlerts = + systemWideAlertService.findAllActive(context, + pageable.getPageSize(), + Math.toIntExact( + pageable.getOffset())); + return converter.toRestPage(systemWideAlerts, pageable, utils.obtainProjection()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + + } + @Override public Class getDomainClass() { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java index 648721f5e60f..0a79452f2190 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java @@ -19,65 +19,50 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.io.IOException; -import java.sql.SQLException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.dspace.alerts.AllowSessionsEnum; import org.dspace.alerts.SystemWideAlert; -import org.dspace.alerts.service.SystemWideAlertService; import org.dspace.app.rest.model.SystemWideAlertRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.authorize.AuthorizeException; -import org.junit.After; +import org.dspace.builder.SystemWideAlertBuilder; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; /** * Test class to test the operations in the SystemWideAlertRestRepository */ public class SystemWideAlertRestRepositoryIT extends AbstractControllerIntegrationTest { - private static final Logger log = LogManager.getLogger(SystemWideAlertRestRepositoryIT.class); - - @Autowired - private SystemWideAlertService systemWideAlertService; - - @After - public void destroy() throws Exception { + @Test + public void findAllTest() throws Exception { + // Create two alert entries in the db to fully test the findAll method + // Note: It is not possible to create two alerts through the REST API context.turnOffAuthorisationSystem(); - systemWideAlertService.findAll(context).stream().forEach(systemWideAlert -> { - try { - systemWideAlertService.delete(context, systemWideAlert); - } catch (SQLException | IOException | AuthorizeException e) { - log.error(e); - } - }); + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(countdownDate) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); context.restoreAuthSystemState(); - super.destroy(); - } + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + String authToken = getAuthToken(admin.getEmail(), password); - @Test - public void findAllTest() throws Exception { - Date countdownDate = new Date(); - SystemWideAlert systemWideAlert1 = systemWideAlertService.create(context, "Test alert 1", - AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY, - countdownDate, - true); - SystemWideAlert systemWideAlert2 = systemWideAlertService.create(context, "Test alert 2", - AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY, - null, - false); - DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - getClient().perform(get("/api/system/systemwidealerts/")) + getClient(authToken).perform(get("/api/system/systemwidealerts/")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.systemwidealerts", containsInAnyOrder( allOf( @@ -96,35 +81,243 @@ public void findAllTest() throws Exception { hasJsonPath("$.active", is(systemWideAlert2.isActive())) ) ))); + } + + @Test + public void findAllUnauthorizedTest() throws Exception { + // Create two alert entries in the db to fully test the findAll method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(countdownDate) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/system/systemwidealerts/")) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void findAllForbiddenTest() throws Exception { + // Create two alert entries in the db to fully test the findAll method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(countdownDate) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + String authToken = getAuthToken(eperson.getEmail(), password); + getClient(authToken).perform(get("/api/system/systemwidealerts/")) + .andExpect(status().isForbidden()); } @Test public void findOneTest() throws Exception { + // Create two alert entries in the db to fully test the findOne method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(countdownDate) + .isActive(true) + .build(); + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + String authToken = getAuthToken(admin.getEmail(), password); + + // When the alert is active and the user is not an admin, the user will be able to see the alert + getClient(authToken).perform(get("/api/system/systemwidealerts/" + systemWideAlert1.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlert1.getAllowSessions())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ) + )); + } + + + @Test + public void findOneUnauthorizedTest() throws Exception { + // Create two alert entries in the db to fully test the findOne method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); Date countdownDate = new Date(); - SystemWideAlert systemWideAlert1 = systemWideAlertService.create(context, "Test alert 1", - AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY, - countdownDate, - true); - SystemWideAlert systemWideAlert2 = systemWideAlertService.create(context, "Test alert 2", - AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY, - null, - false); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(countdownDate) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + // When the alert is active and the user is not an admin, the user will be able to see the alert getClient().perform(get("/api/system/systemwidealerts/" + systemWideAlert1.getID())) - .andExpect(status().isOk()) - .andExpect( - jsonPath("$", allOf( + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlert1.getAllowSessions())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ) + )); + + // When the alert is inactive and the user is not an admin, the user will not be able to see the presence of the + // alert and a 404 will be returned by the findOne endpoint + getClient().perform(get("/api/system/systemwidealerts/" + systemWideAlert2.getID())) + .andExpect(status().isNotFound()); + + } + + @Test + public void findOneForbiddenTest() throws Exception { + // Create two alert entries in the db to fully test the findOne method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(countdownDate) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); + + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + String authToken = getAuthToken(eperson.getEmail(), password); + + + getClient(authToken).perform(get("/api/system/systemwidealerts/" + systemWideAlert1.getID())) + .andExpect(status().isOk()) + .andExpect( + jsonPath("$", allOf( + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", + is(systemWideAlert1.getAllowSessions())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ) + )); + + // When the alert is inactive and the user is not an admin, the user will not be able to see the presence of the + // alert and a 404 will be returned by the findOne endpoint + getClient(authToken).perform(get("/api/system/systemwidealerts/" + systemWideAlert2.getID())) + .andExpect(status().isNotFound()); + + } + + @Test + public void findAllActiveTest() throws Exception { + // Create three alert entries in the db to fully test the findActive search method + // Note: It is not possible to create two alerts through the REST API + context.turnOffAuthorisationSystem(); + Date countdownDate = new Date(); + SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") + .withAllowSessions( + AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) + .withCountdownDate(countdownDate) + .isActive(true) + .build(); + + SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + + SystemWideAlert systemWideAlert3 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 3") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(true) + .build(); + context.restoreAuthSystemState(); + + DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + getClient().perform(get("/api/system/systemwidealerts/search/active")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.systemwidealerts", containsInAnyOrder( + allOf( hasJsonPath("$.alertId", is(systemWideAlert1.getID())), hasJsonPath("$.message", is(systemWideAlert1.getMessage())), hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions())), hasJsonPath("$.countdownTo", startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), hasJsonPath("$.active", is(systemWideAlert1.isActive())) + ), + allOf( + hasJsonPath("$.alertId", is(systemWideAlert3.getID())), + hasJsonPath("$.message", is(systemWideAlert3.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert3.getAllowSessions())), + hasJsonPath("$.countdownTo", is(systemWideAlert3.getCountdownTo())), + hasJsonPath("$.active", is(systemWideAlert3.isActive())) ) - )); + ))); } @@ -162,7 +355,7 @@ public void createTest() throws Exception { .andDo(result -> idRef .set((read(result.getResponse().getContentAsString(), "$.alertId")))); - getClient().perform(get("/api/system/systemwidealerts/" + idRef.get())) + getClient(authToken).perform(get("/api/system/systemwidealerts/" + idRef.get())) .andExpect(status().isOk()) .andExpect( jsonPath("$", allOf( @@ -218,11 +411,16 @@ public void createUnAuthorizedTest() throws Exception { @Test public void createWhenAlreadyExistsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + SystemWideAlert systemWideAlert = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); - SystemWideAlert systemWideAlert = systemWideAlertService.create(context, "Test alert", - AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY, - null, - false); + context.restoreAuthSystemState(); SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); systemWideAlertRest.setMessage("Alert test message"); @@ -243,11 +441,14 @@ public void createWhenAlreadyExistsTest() throws Exception { @Test public void putTest() throws Exception { - - SystemWideAlert systemWideAlert = systemWideAlertService.create(context, "Alert test message", - AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY, - null, - false); + context.turnOffAuthorisationSystem(); + SystemWideAlert systemWideAlert = SystemWideAlertBuilder.createSystemWideAlert(context, "Alert test message") + .withAllowSessions( + AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY) + .withCountdownDate(null) + .isActive(false) + .build(); + context.restoreAuthSystemState(); SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); systemWideAlertRest.setAlertId(systemWideAlert.getID()); @@ -278,7 +479,7 @@ public void putTest() throws Exception { ) )); - getClient().perform(get("/api/system/systemwidealerts/" + systemWideAlert.getID())) + getClient(authToken).perform(get("/api/system/systemwidealerts/" + systemWideAlert.getID())) .andExpect(status().isOk()) .andExpect( jsonPath("$", allOf( From 0944f5956fd20ca78374d67ae5122c8ea5dd168e Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 1 Feb 2023 17:11:24 +0100 Subject: [PATCH 205/394] [CST-7756] minor update --- .../src/main/java/org/dspace/eperson/Subscription.java | 2 +- .../dSpaceObjectsUpdates/CollectionsUpdates.java | 8 +++----- .../dSpaceObjectsUpdates/CommunityUpdates.java | 6 ++---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index 7ca19c6020e6..9856e03e0e0e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -41,7 +41,7 @@ public class Subscription implements ReloadableEntity { @SequenceGenerator(name = "subscription_seq", sequenceName = "subscription_seq", allocationSize = 1) private Integer id; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "dspace_object_id") private DSpaceObject dSpaceObject; diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java index 31aa78f52135..974006e7a66d 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java @@ -18,6 +18,7 @@ import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; import org.dspace.subscriptions.service.DSpaceObjectUpdates; +import org.springframework.beans.factory.annotation.Autowired; /** * Class which will be used to find @@ -27,7 +28,8 @@ */ public class CollectionsUpdates implements DSpaceObjectUpdates { - private final SearchService searchService; + @Autowired + private SearchService searchService; @Override @SuppressWarnings("rawtypes") @@ -41,8 +43,4 @@ public List findUpdates(Context context, DSpaceObject dSpaceObj return discoverResult.getIndexableObjects(); } - public CollectionsUpdates(SearchService searchService) { - this.searchService = searchService; - } - } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java index ada51cc5f7ab..40bd1ed30c6e 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java @@ -18,6 +18,7 @@ import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; import org.dspace.subscriptions.service.DSpaceObjectUpdates; +import org.springframework.beans.factory.annotation.Autowired; /** * Class which will be used to find @@ -27,6 +28,7 @@ */ public class CommunityUpdates implements DSpaceObjectUpdates { + @Autowired private SearchService searchService; @Override @@ -41,8 +43,4 @@ public List findUpdates(Context context, DSpaceObject dSpaceObj return discoverResult.getIndexableObjects(); } - public CommunityUpdates(SearchService searchService) { - this.searchService = searchService; - } - } \ No newline at end of file From 7a807f61a82bc775e9dc2653cf517960a60f22cf Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 1 Feb 2023 17:12:04 +0100 Subject: [PATCH 206/394] [CST-7756] added missing index field --- dspace/solr/search/conf/schema.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index 7c0b9a0ccd4a..ffd1a6466be7 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -280,6 +280,7 @@ + @@ -355,5 +356,6 @@ + From de44d9dbe75a33a30d76865b2e07a92e56cb8415 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 1 Feb 2023 17:12:46 +0100 Subject: [PATCH 207/394] [CST-7756] added template for subscription content email --- dspace/config/emails/subscriptions_content | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 dspace/config/emails/subscriptions_content diff --git a/dspace/config/emails/subscriptions_content b/dspace/config/emails/subscriptions_content new file mode 100644 index 000000000000..76d682aaa1b0 --- /dev/null +++ b/dspace/config/emails/subscriptions_content @@ -0,0 +1,19 @@ +## E-mail sent to designated address about updates on subscribed items +## +## Parameters: {0} Collections updates +## {1} Communities updates +## {2} Items updates + +This email is sent from DSpace based on the chosen subscription preferences. + +Communities +----------- +List of changed items : ${params[0]} + +Collections +----------- +List of changed items : ${params[1]} + +Items +----- +List of changed items : ${params[2]} From da2815752c7389626cef9ee4f5981a25564262f3 Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Wed, 1 Feb 2023 18:13:26 +0200 Subject: [PATCH 208/394] [CST-7754] removed old code of supervision orders added a new migration script sql tested that supervisors can edit only workspace items --- .../exception/ResourceConflictException.java | 39 -- .../content/InstallItemServiceImpl.java | 14 + .../content/SupervisedItemServiceImpl.java | 40 -- .../org/dspace/content/WorkspaceItem.java | 24 -- .../content/WorkspaceItemServiceImpl.java | 22 - .../dspace/content/dao/WorkspaceItemDAO.java | 4 - .../dao/impl/WorkspaceItemDAOImpl.java | 30 -- .../factory/ContentServiceFactory.java | 3 - .../factory/ContentServiceFactoryImpl.java | 8 - .../service/SupervisedItemService.java | 44 -- .../content/service/WorkspaceItemService.java | 4 - ...ServiceSupervisionOrderIndexingPlugin.java | 6 +- .../main/java/org/dspace/eperson/Group.java | 8 - .../org/dspace/eperson/GroupServiceImpl.java | 3 - .../dspace/eperson/SupervisorServiceImpl.java | 93 ----- .../factory/EPersonServiceFactory.java | 3 - .../factory/EPersonServiceFactoryImpl.java | 7 - .../eperson/service/SupervisorService.java | 83 ---- .../supervision/dao/SupervisionOrderDao.java | 18 + .../enumeration/SupervisionOrderType.java | 6 + .../service/SupervisionOrderService.java | 36 ++ ...5_2022.12.09__Supervision_Orders_table.sql | 58 +++ ...5_2022.12.09__Supervision_Orders_table.sql | 65 +++ .../dspace/content/SupervisedItemTest.java | 200 --------- .../SupervisionOrderServiceIT.java | 385 ++++++++++++++++++ .../DSpaceApiExceptionControllerAdvice.java | 19 - .../SupervisionOrderRestRepository.java | 12 +- .../SupervisionOrderRestRepositoryIT.java | 206 ++++++++-- .../rest/WorkflowItemRestRepositoryIT.java | 147 +++++-- dspace/config/spring/api/core-services.xml | 2 - dspace/config/spring/api/discovery.xml | 6 +- 31 files changed, 888 insertions(+), 707 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java delete mode 100644 dspace-api/src/main/java/org/dspace/content/SupervisedItemServiceImpl.java delete mode 100644 dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java delete mode 100644 dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java delete mode 100644 dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java delete mode 100644 dspace-api/src/test/java/org/dspace/content/SupervisedItemTest.java create mode 100644 dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java diff --git a/dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java b/dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java deleted file mode 100644 index ef0dd116d915..000000000000 --- a/dspace-api/src/main/java/org/dspace/app/exception/ResourceConflictException.java +++ /dev/null @@ -1,39 +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.exception; - -/** - * This class provides an exception to be used when a conflict on a resource - * occurs. - * - * @author Mohamed Eskander (mohamed.eskander at 4science dot it) - * - */ -public class ResourceConflictException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - private final Object resource; - - /** - * Create a ResourceConflictException with a message and the conflicting - * resource. - * - * @param message the error message - * @param resource the resource that caused the conflict - */ - public ResourceConflictException(String message, Object resource) { - super(message); - this.resource = resource; - } - - public Object getResource() { - return resource; - } - -} diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index 11cd4c107c34..8966192e204c 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -22,6 +22,8 @@ import org.dspace.event.Event; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.IdentifierService; +import org.dspace.supervision.SupervisionOrder; +import org.dspace.supervision.service.SupervisionOrderService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -42,6 +44,8 @@ public class InstallItemServiceImpl implements InstallItemService { protected IdentifierService identifierService; @Autowired(required = true) protected ItemService itemService; + @Autowired(required = true) + protected SupervisionOrderService supervisionOrderService; protected InstallItemServiceImpl() { @@ -222,9 +226,19 @@ protected Item finishItem(Context c, Item item, InProgressSubmission is) // set embargo lift date and take away read access if indicated. embargoService.setEmbargo(c, item); + // delete all related supervision orders + deleteSupervisionOrders(c, item); + return item; } + private void deleteSupervisionOrders(Context c, Item item) throws SQLException, AuthorizeException { + List supervisionOrders = supervisionOrderService.findByItem(c, item); + for (SupervisionOrder supervisionOrder : supervisionOrders) { + supervisionOrderService.delete(c, supervisionOrder); + } + } + @Override public String getBitstreamProvenanceMessage(Context context, Item myitem) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/content/SupervisedItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/SupervisedItemServiceImpl.java deleted file mode 100644 index b0eb77ec2aa8..000000000000 --- a/dspace-api/src/main/java/org/dspace/content/SupervisedItemServiceImpl.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.content; - -import java.sql.SQLException; -import java.util.List; - -import org.dspace.content.service.SupervisedItemService; -import org.dspace.content.service.WorkspaceItemService; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; -import org.springframework.beans.factory.annotation.Autowired; - -public class SupervisedItemServiceImpl implements SupervisedItemService { - - @Autowired(required = true) - protected WorkspaceItemService workspaceItemService; - - protected SupervisedItemServiceImpl() { - - } - - @Override - public List getAll(Context context) - throws SQLException { - return workspaceItemService.findAllSupervisedItems(context); - } - - @Override - public List findbyEPerson(Context context, EPerson ep) - throws SQLException { - return workspaceItemService.findSupervisedItemsByEPerson(context, ep); - } - -} diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java index 8049aa976caf..a4c880173bf7 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java @@ -8,8 +8,6 @@ package org.dspace.content; import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -17,8 +15,6 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; import javax.persistence.SequenceGenerator; @@ -27,7 +23,6 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; import org.dspace.workflow.WorkflowItem; import org.hibernate.proxy.HibernateProxyHelper; @@ -78,14 +73,6 @@ public class WorkspaceItem @Column(name = "page_reached") private Integer pageReached = -1; - @ManyToMany(fetch = FetchType.LAZY) - @JoinTable( - name = "epersongroup2workspaceitem", - joinColumns = {@JoinColumn(name = "workspace_item_id")}, - inverseJoinColumns = {@JoinColumn(name = "eperson_group_id")} - ) - private final List supervisorGroups = new ArrayList<>(); - /** * Protected constructor, create object using: * {@link org.dspace.content.service.WorkspaceItemService#create(Context, Collection, boolean)} @@ -226,15 +213,4 @@ public void setPublishedBefore(boolean b) { publishedBefore = b; } - public List getSupervisorGroups() { - return supervisorGroups; - } - - void removeSupervisorGroup(Group group) { - supervisorGroups.remove(group); - } - - void addSupervisorGroup(Group group) { - supervisorGroups.add(group); - } } diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index f40bb5256f3b..18967bd6aa54 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -212,16 +212,6 @@ public WorkspaceItem findByItem(Context context, Item item) throws SQLException return workspaceItemDAO.findByItem(context, item); } - @Override - public List findAllSupervisedItems(Context context) throws SQLException { - return workspaceItemDAO.findWithSupervisedGroup(context); - } - - @Override - public List findSupervisedItemsByEPerson(Context context, EPerson ePerson) throws SQLException { - return workspaceItemDAO.findBySupervisedGroupMember(context, ePerson); - } - @Override public List findAll(Context context) throws SQLException { return workspaceItemDAO.findAll(context); @@ -268,10 +258,6 @@ public void deleteAll(Context context, WorkspaceItem workspaceItem) "workspace_item_id=" + workspaceItem.getID() + "item_id=" + item.getID() + "collection_id=" + workspaceItem.getCollection().getID())); - // Need to delete the epersongroup2workspaceitem row first since it refers - // to workspaceitem ID - workspaceItem.getSupervisorGroups().clear(); - // Need to delete the workspaceitem row first since it refers // to item ID workspaceItemDAO.delete(context, workspaceItem); @@ -307,14 +293,6 @@ public void deleteWrapper(Context context, WorkspaceItem workspaceItem) throws S // deleteSubmitPermissions(); - // Need to delete the workspaceitem row first since it refers - // to item ID - try { - workspaceItem.getSupervisorGroups().clear(); - } catch (Exception e) { - log.error("failed to clear supervisor group", e); - } - workspaceItemDAO.delete(context, workspaceItem); } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java index 4ae8dc620b21..900858b72869 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java @@ -41,10 +41,6 @@ public List findByEPerson(Context context, EPerson ep, Integer li public List findAll(Context context, Integer limit, Integer offset) throws SQLException; - public List findWithSupervisedGroup(Context context) throws SQLException; - - public List findBySupervisedGroupMember(Context context, EPerson ePerson) throws SQLException; - int countRows(Context context) throws SQLException; List> getStageReachedCounts(Context context) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java index de1b9a5aea9e..138451365522 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java @@ -15,7 +15,6 @@ import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; import javax.persistence.criteria.Root; import org.dspace.content.Collection; @@ -26,8 +25,6 @@ import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.dspace.eperson.EPerson_; -import org.dspace.eperson.Group; /** * Hibernate implementation of the Database Access Object interface class for the WorkspaceItem object. @@ -114,33 +111,6 @@ public List findAll(Context context, Integer limit, Integer offse return list(context, criteriaQuery, false, WorkspaceItem.class, limit, offset); } - @Override - public List findWithSupervisedGroup(Context context) throws SQLException { - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, WorkspaceItem.class); - Root workspaceItemRoot = criteriaQuery.from(WorkspaceItem.class); - criteriaQuery.select(workspaceItemRoot); - criteriaQuery.where(criteriaBuilder.isNotEmpty(workspaceItemRoot.get(WorkspaceItem_.supervisorGroups))); - - List orderList = new LinkedList<>(); - orderList.add(criteriaBuilder.asc(workspaceItemRoot.get(WorkspaceItem_.workspaceItemId))); - criteriaQuery.orderBy(orderList); - return list(context, criteriaQuery, false, WorkspaceItem.class, -1, -1); - } - - @Override - public List findBySupervisedGroupMember(Context context, EPerson ePerson) throws SQLException { - CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); - CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, WorkspaceItem.class); - Root workspaceItemRoot = criteriaQuery.from(WorkspaceItem.class); - Join join = workspaceItemRoot.join("supervisorGroups"); - Join secondJoin = join.join("epeople"); - criteriaQuery.select(workspaceItemRoot); - criteriaQuery.where(criteriaBuilder.equal(secondJoin.get(EPerson_.id), ePerson.getID())); - criteriaQuery.orderBy(criteriaBuilder.asc(workspaceItemRoot.get(WorkspaceItem_.workspaceItemId))); - return list(context, criteriaQuery, false, WorkspaceItem.class, -1, -1); - } - @Override public int countRows(Context context) throws SQLException { return count(createQuery(context, "SELECT count(*) from WorkspaceItem")); diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java index 4010e148610c..37fcdd5c6c31 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java @@ -31,7 +31,6 @@ import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.SiteService; -import org.dspace.content.service.SupervisedItemService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -71,8 +70,6 @@ public abstract class ContentServiceFactory { public abstract InstallItemService getInstallItemService(); - public abstract SupervisedItemService getSupervisedItemService(); - public abstract SiteService getSiteService(); /** diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java index 6f123ae1bac7..1f604e4271ba 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java @@ -28,7 +28,6 @@ import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.SiteService; -import org.dspace.content.service.SupervisedItemService; import org.dspace.content.service.WorkspaceItemService; import org.springframework.beans.factory.annotation.Autowired; @@ -68,8 +67,6 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory { @Autowired(required = true) private InstallItemService installItemService; @Autowired(required = true) - private SupervisedItemService supervisedItemService; - @Autowired(required = true) private SiteService siteService; @Autowired(required = true) @@ -148,11 +145,6 @@ public InstallItemService getInstallItemService() { return installItemService; } - @Override - public SupervisedItemService getSupervisedItemService() { - return supervisedItemService; - } - @Override public SiteService getSiteService() { return siteService; diff --git a/dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java b/dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java deleted file mode 100644 index 883e0f9fd2fb..000000000000 --- a/dspace-api/src/main/java/org/dspace/content/service/SupervisedItemService.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.content.service; - -import java.sql.SQLException; -import java.util.List; - -import org.dspace.content.WorkspaceItem; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; - -/** - * Class to handle WorkspaceItems which are being supervised. - * - * @author Richard Jones - * @version $Revision$ - */ -public interface SupervisedItemService { - /** - * Get all workspace items which are being supervised - * - * @param context the context this object exists in - * @return array of SupervisedItems - * @throws SQLException if database error - */ - public List getAll(Context context) throws SQLException; - - - /** - * Get items being supervised by given EPerson - * - * @param ep the eperson who's items to supervise we want - * @param context the dspace context - * @return the items eperson is supervising in an array - * @throws SQLException if database error - */ - public List findbyEPerson(Context context, EPerson ep) - throws SQLException; -} diff --git a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java index 8f572f6108ac..c8df68e43498 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java @@ -127,10 +127,6 @@ public List findByCollection(Context context, Collection collecti public WorkspaceItem findByItem(Context context, Item item) throws SQLException; - public List findAllSupervisedItems(Context context) throws SQLException; - - public List findSupervisedItemsByEPerson(Context context, EPerson ePerson) throws SQLException; - /** * Get all workspace items in the whole system * diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java index 99204885f8c9..5af8b61ebbb7 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java @@ -23,8 +23,12 @@ import org.springframework.beans.factory.annotation.Autowired; /** + * A Solr Indexing plugin responsible for + * When item being indexed is a workspace or workflow item, + * and at least one supervision order is defined + * a 'supervised' field with value 'true' will be added to the solr document, + * if not a 'supervised' will be 'false' * - * * @author Mohamed Eskander (mohamed.eskander at 4science dot it) */ public class SolrServiceSupervisionOrderIndexingPlugin implements SolrServiceIndexPlugin { diff --git a/dspace-api/src/main/java/org/dspace/eperson/Group.java b/dspace-api/src/main/java/org/dspace/eperson/Group.java index b2d39648958b..6cb534146b25 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group.java @@ -23,7 +23,6 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObjectLegacySupport; -import org.dspace.content.WorkspaceItem; import org.dspace.core.Constants; import org.dspace.core.Context; import org.hibernate.annotations.CacheConcurrencyStrategy; @@ -83,9 +82,6 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport { @ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups") private final List parentGroups = new ArrayList<>(); - @ManyToMany(fetch = FetchType.LAZY, mappedBy = "supervisorGroups") - private final List supervisedItems = new ArrayList<>(); - @Transient private boolean groupsChanged; @@ -218,10 +214,6 @@ public Integer getLegacyId() { return legacyId; } - public List getSupervisedItems() { - return supervisedItems; - } - /** * May this Group be renamed or deleted? (The content of any group may be * changed.) diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index 2b23ecfeef18..f3dc6ca36a65 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -480,9 +480,6 @@ public void delete(Context context, Group group) throws SQLException { context.addEvent(new Event(Event.DELETE, Constants.GROUP, group.getID(), group.getName(), getIdentifiers(context, group))); - //Remove the supervised group from any workspace items linked to us. - group.getSupervisedItems().clear(); - // Remove any ResourcePolicies that reference this group authorizeService.removeGroupPolicies(context, group); diff --git a/dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java deleted file mode 100644 index 64180a5e2231..000000000000 --- a/dspace-api/src/main/java/org/dspace/eperson/SupervisorServiceImpl.java +++ /dev/null @@ -1,93 +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.eperson; - -import java.sql.SQLException; - -import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.ResourcePolicy; -import org.dspace.authorize.service.ResourcePolicyService; -import org.dspace.content.Item; -import org.dspace.content.WorkspaceItem; -import org.dspace.content.service.ItemService; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.eperson.service.SupervisorService; -import org.springframework.beans.factory.annotation.Autowired; - -public class SupervisorServiceImpl implements SupervisorService { - - @Autowired(required = true) - protected ItemService itemService; - @Autowired(required = true) - protected ResourcePolicyService resourcePolicyService; - - protected SupervisorServiceImpl() { - } - - @Override - public boolean isOrder(Context context, WorkspaceItem workspaceItem, Group group) - throws SQLException { - return workspaceItem.getSupervisorGroups().contains(group); - } - - @Override - public void remove(Context context, WorkspaceItem workspaceItem, Group group) - throws SQLException, AuthorizeException { - // get the workspace item and the group from the request values - workspaceItem.getSupervisorGroups().remove(group); - - // get the item and have it remove the policies for the group - Item item = workspaceItem.getItem(); - itemService.removeGroupPolicies(context, item, group); - } - - @Override - public void add(Context context, Group group, WorkspaceItem workspaceItem, int policy) - throws SQLException, AuthorizeException { - // make a table row in the database table, and update with the relevant - // details - workspaceItem.getSupervisorGroups().add(group); - group.getSupervisedItems().add(workspaceItem); - - // If a default policy type has been requested, apply the policies using - // the DSpace API for doing so - if (policy != POLICY_NONE) { - Item item = workspaceItem.getItem(); - - // "Editor" implies READ, WRITE, ADD permissions - // "Observer" implies READ permissions - if (policy == POLICY_EDITOR) { - ResourcePolicy r = resourcePolicyService.create(context); - r.setdSpaceObject(item); - r.setGroup(group); - r.setAction(Constants.READ); - resourcePolicyService.update(context, r); - - r = resourcePolicyService.create(context); - r.setdSpaceObject(item); - r.setGroup(group); - r.setAction(Constants.WRITE); - resourcePolicyService.update(context, r); - - r = resourcePolicyService.create(context); - r.setdSpaceObject(item); - r.setGroup(group); - r.setAction(Constants.ADD); - resourcePolicyService.update(context, r); - - } else if (policy == POLICY_OBSERVER) { - ResourcePolicy r = resourcePolicyService.create(context); - r.setdSpaceObject(item); - r.setGroup(group); - r.setAction(Constants.READ); - resourcePolicyService.update(context, r); - } - } - } -} diff --git a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java index f7ce13a8a397..b80c37f13ff5 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactory.java @@ -12,7 +12,6 @@ import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; import org.dspace.eperson.service.SubscribeService; -import org.dspace.eperson.service.SupervisorService; import org.dspace.services.factory.DSpaceServicesFactory; /** @@ -33,8 +32,6 @@ public abstract class EPersonServiceFactory { public abstract SubscribeService getSubscribeService(); - public abstract SupervisorService getSupervisorService(); - public static EPersonServiceFactory getInstance() { return DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("ePersonServiceFactory", EPersonServiceFactory.class); diff --git a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java index 33d9249b6bfd..c4a6cbe9964c 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/factory/EPersonServiceFactoryImpl.java @@ -12,7 +12,6 @@ import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; import org.dspace.eperson.service.SubscribeService; -import org.dspace.eperson.service.SupervisorService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -33,8 +32,6 @@ public class EPersonServiceFactoryImpl extends EPersonServiceFactory { private AccountService accountService; @Autowired(required = true) private SubscribeService subscribeService; - @Autowired(required = true) - private SupervisorService supervisorService; @Override public EPersonService getEPersonService() { @@ -61,8 +58,4 @@ public SubscribeService getSubscribeService() { return subscribeService; } - @Override - public SupervisorService getSupervisorService() { - return supervisorService; - } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java deleted file mode 100644 index 470c9133e59a..000000000000 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SupervisorService.java +++ /dev/null @@ -1,83 +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.eperson.service; - -import java.sql.SQLException; - -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.WorkspaceItem; -import org.dspace.core.Context; -import org.dspace.eperson.Group; - -/** - * Class to represent the supervisor, primarily for use in applying supervisor - * activities to the database, such as setting and unsetting supervision - * orders and so forth. - * - * @author Richard Jones - * @version $Revision$ - */ -public interface SupervisorService { - - /** - * value to use for no policy set - */ - public static final int POLICY_NONE = 0; - - /** - * value to use for editor policies - */ - public static final int POLICY_EDITOR = 1; - - /** - * value to use for observer policies - */ - public static final int POLICY_OBSERVER = 2; - - /** - * finds out if there is a supervision order that matches this set - * of values - * - * @param context the context this object exists in - * @param workspaceItem the workspace item to be supervised - * @param group the group to be doing the supervising - * @return boolean true if there is an order that matches, false if not - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - public boolean isOrder(Context context, WorkspaceItem workspaceItem, Group group) - throws SQLException; - - /** - * removes the requested group from the requested workspace item in terms - * of supervision. This also removes all the policies that group has - * associated with the item - * - * @param context the context this object exists in - * @param workspaceItem the ID of the workspace item - * @param group the ID of the group to be removed from the item - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - public void remove(Context context, WorkspaceItem workspaceItem, Group group) - throws SQLException, AuthorizeException; - - /** - * adds a supervision order to the database - * - * @param context the context this object exists in - * @param group the ID of the group which will supervise - * @param workspaceItem the ID of the workspace item to be supervised - * @param policy String containing the policy type to be used - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - public void add(Context context, Group group, WorkspaceItem workspaceItem, int policy) - throws SQLException, AuthorizeException; -} diff --git a/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java b/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java index 185e9d72a6af..2dd5dad12a4d 100644 --- a/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java +++ b/dspace-api/src/main/java/org/dspace/supervision/dao/SupervisionOrderDao.java @@ -26,7 +26,25 @@ */ public interface SupervisionOrderDao extends GenericDAO { + /** + * find all Supervision Orders related to the item + * + * @param context The DSpace context + * @param item the item + * @return the Supervision Orders related to the item + * @throws SQLException If something goes wrong in the database + */ List findByItem(Context context, Item item) throws SQLException; + + /** + * find the Supervision Order related to the item and group + * + * @param context The DSpace context + * @param item the item + * @param group the group + * @return the Supervision Order related to the item and group + * @throws SQLException If something goes wrong in the database + */ SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java index a071d1695d5f..53d45bd2a2df 100644 --- a/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java +++ b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java @@ -9,6 +9,12 @@ /** + * This Enum holds a representation of all the possible supervision order types + *

+ * OBSERVER: grant READ permission to the item + * EDITOR: grant READ and WRITE permissions to the item + * NONE: no grants + *

* * @author Mohamed Eskander (mohamed.eskander at 4science dot it) */ diff --git a/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java index f99090d6e600..6211351435b8 100644 --- a/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java +++ b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java @@ -24,9 +24,45 @@ */ public interface SupervisionOrderService extends DSpaceCRUDService { + /** + * @param context The DSpace context + * @param item the item + * @param group the group + * @return the created Supervision Order on item and group + * @throws SQLException If something goes wrong in the database + */ SupervisionOrder create(Context context, Item item, Group group) throws SQLException; + + /** + * @param context The DSpace context + * @return all Supervision Orders + * @throws SQLException If something goes wrong in the database + */ List findAll(Context context) throws SQLException; + + /** + * @param context The DSpace context + * @param item the item + * @return all Supervision Orders related to the item + * @throws SQLException If something goes wrong in the database + */ List findByItem(Context context, Item item) throws SQLException; + + /** + * @param context The DSpace context + * @param item the item + * @param group the group + * @return the Supervision Order of the item and group + * @throws SQLException If something goes wrong in the database + */ SupervisionOrder findByItemAndGroup(Context context, Item item, Group group) throws SQLException; + + /** + * @param context The DSpace context + * @param ePerson the ePerson to be checked + * @param item the item + * @return true if the ePerson is a supervisor of the item + * @throws SQLException If something goes wrong in the database + */ boolean isSupervisor(Context context, EPerson ePerson, Item item) throws SQLException; } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql index 33d3eb5c82c8..c7bb0b502ec2 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -18,3 +18,61 @@ CREATE TABLE supervision_orders ); CREATE SEQUENCE supervision_orders_seq; + +INSERT INTO supervision_orders (id, item_id, eperson_group_id) +SELECT supervision_orders_seq.nextval AS id, w.item_id, e.uuid +FROM epersongroup2workspaceitem ew INNER JOIN workspaceitem w +ON ew.workspace_item_id = w.workspace_item_id +INNER JOIN epersongroup e +ON ew.eperson_group_id = e.uuid; + + +-- UPDATE policies for supervision orders +-- items, bundles and bitstreams + +DECLARE +BEGIN + +FOR rec IN +( +SELECT so.item_id as dspace_object, so.eperson_group_id, rp.resource_type_id +FROM supervision_orders so +INNER JOIN RESOURCEPOLICY rp on so.item_id = rp.dspace_object +AND so.eperson_group_id = rp.epersongroup_id +WHERE rp.rptype IS NULL + +UNION + +SELECT ib.bundle_id as dspace_object, so.eperson_group_id, rp.resource_type_id +FROM supervision_orders so +INNER JOIN item2bundle ib ON so.item_id = ib.item_id +INNER JOIN RESOURCEPOLICY rp on ib.bundle_id = rp.dspace_object +AND so.eperson_group_id = rp.epersongroup_id +WHERE rp.rptype IS NULL + +UNION + +SELECT bs.bitstream_id as dspace_object, so.eperson_group_id, rp.resource_type_id +FROM supervision_orders so +INNER JOIN item2bundle ib ON so.item_id = ib.item_id +INNER JOIN bundle2bitstream bs ON ib.bundle_id = bs.bundle_id +INNER JOIN RESOURCEPOLICY rp on bs.bitstream_id = rp.dspace_object +AND so.eperson_group_id = rp.epersongroup_id +WHERE rp.rptype IS NULL +) + +LOOP + +UPDATE RESOURCEPOLICY SET rptype = 'TYPE_SUBMISSION' +where dspace_object = rec.dspace_object +AND epersongroup_id = rec.eperson_group_id +AND rptype IS NULL; + +END LOOP; +END; + +------------------------------------------------------------------------------- +-- drop epersongroup2workspaceitem table +------------------------------------------------------------------------------- + +DROP TABLE epersongroup2workspaceitem; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql index 33d3eb5c82c8..f27a4f2a1bb6 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.09__Supervision_Orders_table.sql @@ -18,3 +18,68 @@ CREATE TABLE supervision_orders ); CREATE SEQUENCE supervision_orders_seq; + +------------------------------------------------------------------------------- +-- migrate data from epersongroup2workspaceitem table +------------------------------------------------------------------------------- + +INSERT INTO supervision_orders (id, item_id, eperson_group_id) +SELECT getnextid('supervision_orders') AS id, w.item_id, e.uuid +FROM epersongroup2workspaceitem ew INNER JOIN workspaceitem w +ON ew.workspace_item_id = w.workspace_item_id +INNER JOIN epersongroup e +ON ew.eperson_group_id = e.uuid; + + +-- UPDATE policies for supervision orders +-- items, bundles and bitstreams + +do +$$ +DECLARE +rec record; +BEGIN + +FOR rec IN + +SELECT so.item_id as dspace_object, so.eperson_group_id, rp.resource_type_id +FROM supervision_orders so +INNER JOIN RESOURCEPOLICY rp on so.item_id = rp.dspace_object +AND so.eperson_group_id = rp.epersongroup_id +WHERE rp.rptype IS NULL + +UNION + +SELECT ib.bundle_id as dspace_object, so.eperson_group_id, rp.resource_type_id +FROM supervision_orders so +INNER JOIN item2bundle ib ON so.item_id = ib.item_id +INNER JOIN RESOURCEPOLICY rp on ib.bundle_id = rp.dspace_object +AND so.eperson_group_id = rp.epersongroup_id +WHERE rp.rptype IS NULL + +UNION + +SELECT bs.bitstream_id as dspace_object, so.eperson_group_id, rp.resource_type_id +FROM supervision_orders so +INNER JOIN item2bundle ib ON so.item_id = ib.item_id +INNER JOIN bundle2bitstream bs ON ib.bundle_id = bs.bundle_id +INNER JOIN RESOURCEPOLICY rp on bs.bitstream_id = rp.dspace_object +AND so.eperson_group_id = rp.epersongroup_id +WHERE rp.rptype IS NULL + +LOOP + +UPDATE RESOURCEPOLICY SET rptype = 'TYPE_SUBMISSION' +where dspace_object = rec.dspace_object +AND epersongroup_id = rec.eperson_group_id +AND rptype IS NULL; + +END LOOP; +END; +$$; + +------------------------------------------------------------------------------- +-- drop epersongroup2workspaceitem table +------------------------------------------------------------------------------- + +DROP TABLE epersongroup2workspaceitem; \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/content/SupervisedItemTest.java b/dspace-api/src/test/java/org/dspace/content/SupervisedItemTest.java deleted file mode 100644 index aece739f25af..000000000000 --- a/dspace-api/src/test/java/org/dspace/content/SupervisedItemTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.content; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.List; -import java.util.UUID; - -import org.apache.logging.log4j.Logger; -import org.dspace.AbstractUnitTest; -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.SupervisedItemService; -import org.dspace.content.service.WorkspaceItemService; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.EPersonService; -import org.dspace.eperson.service.GroupService; -import org.dspace.eperson.service.SupervisorService; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -/** - * @author pvillega - */ -public class SupervisedItemTest extends AbstractUnitTest { - - /** - * log4j category - */ - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SupervisedItemTest.class); - - protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); - protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); - protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); - protected SupervisedItemService supervisedItemService = ContentServiceFactory.getInstance() - .getSupervisedItemService(); - protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); - protected SupervisorService supervisorService = EPersonServiceFactory.getInstance().getSupervisorService(); - - protected UUID communityId; - protected UUID groupId; - protected int workspaceItemId; - - - /** - * This method will be run before every test as per @Before. It will - * initialize resources required for the tests. - * - * Other methods can be annotated with @Before here or in subclasses - * but no execution order is guaranteed - */ - @Before - @Override - public void init() { - super.init(); - try { - //we have to create a new community in the database - context.turnOffAuthorisationSystem(); - Community owningCommunity = communityService.create(null, context); - Collection collection = collectionService.create(context, owningCommunity); - WorkspaceItem si = workspaceItemService.create(context, collection, false); - Group gr = groupService.create(context); - EPerson currentUser = context.getCurrentUser(); - groupService.addMember(context, gr, currentUser); - groupService.update(context, gr); - - //set a supervisor as editor - supervisorService.add(context, gr, si, 1); - - communityId = owningCommunity.getID(); - workspaceItemId = si.getID(); - groupId = gr.getID(); - - //we need to commit the changes so we don't block the table for testing - context.restoreAuthSystemState(); - context.complete(); - context = new Context(); - context.setCurrentUser(currentUser); - } catch (AuthorizeException ex) { - log.error("Authorization Error in init", ex); - fail("Authorization Error in init: " + ex.getMessage()); - } catch (SQLException ex) { - log.error("SQL Error in init", ex); - fail("SQL Error in init"); - } - } - - /** - * This method will be run after every test as per @After. It will - * clean resources initialized by the @Before methods. - * - * Other methods can be annotated with @After here or in subclasses - * but no execution order is guaranteed - */ - @After - @Override - public void destroy() { - try { - context.turnOffAuthorisationSystem(); - communityService.delete(context, communityService.find(context, communityId)); - context.restoreAuthSystemState(); - } catch (SQLException | AuthorizeException | IOException ex) { - log.error("SQL Error in destroy", ex); - fail("SQL Error in destroy: " + ex.getMessage()); - } - super.destroy(); - } - - /** - * Test of getAll method, of class SupervisedItem. - */ - @Test - public void testGetAll() throws Exception { - List found = supervisedItemService.getAll(context); - assertThat("testGetAll 0", found, notNullValue()); - assertTrue("testGetAll 1", found.size() >= 1); - - boolean added = false; - for (WorkspaceItem sia : found) { - if (sia.getID() == workspaceItemId) { - added = true; - } - } - assertTrue("testGetAll 2", added); - } - - /** - * Test of getSupervisorGroups method, of class SupervisedItem. - */ - @Test - public void testGetSupervisorGroups_Context_int() throws Exception { - List found = workspaceItemService.find(context, workspaceItemId).getSupervisorGroups(); - assertThat("testGetSupervisorGroups_Context_int 0", found, notNullValue()); - assertTrue("testGetSupervisorGroups_Context_int 1", found.size() == 1); - assertThat("testGetSupervisorGroups_Context_int 2", found.get(0).getID(), equalTo(groupId)); - } - - /** - * Test of getSupervisorGroups method, of class SupervisedItem. - */ - @Test - public void testGetSupervisorGroups_0args() throws Exception { - List found = workspaceItemService.find(context, workspaceItemId).getSupervisorGroups(); - assertThat("testGetSupervisorGroups_0args 0", found, notNullValue()); - assertTrue("testGetSupervisorGroups_0args 1", found.size() == 1); - - boolean added = false; - for (Group g : found) { - if (g.getID().equals(groupId)) { - added = true; - } - } - assertTrue("testGetSupervisorGroups_0args 2", added); - } - - /** - * Test of findbyEPerson method, of class SupervisedItem. - */ - @Test - public void testFindbyEPerson() throws Exception { - context.turnOffAuthorisationSystem(); - List found = supervisedItemService.findbyEPerson(context, ePersonService.create(context)); - assertThat("testFindbyEPerson 0", found, notNullValue()); - assertTrue("testFindbyEPerson 1", found.size() == 0); - - found = supervisedItemService.findbyEPerson(context, context.getCurrentUser()); - assertThat("testFindbyEPerson 2", found, notNullValue()); - assertTrue("testFindbyEPerson 3", found.size() >= 1); - - boolean added = false; - for (WorkspaceItem sia : found) { - if (sia.getID() == workspaceItemId) { - added = true; - } - } - assertTrue("testFindbyEPerson 4", added); - - context.restoreAuthSystemState(); - } - -} diff --git a/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java b/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java new file mode 100644 index 000000000000..60407823485b --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/supervision/SupervisionOrderServiceIT.java @@ -0,0 +1,385 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.supervision; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.GroupBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.supervision.factory.SupervisionOrderServiceFactory; +import org.dspace.supervision.service.SupervisionOrderService; +import org.junit.Test; + +/** + * Unit tests for the {@link SupervisionOrderService} + * + * @author Mohamed Eskander (mohamed.eskander at 4science dot it) + */ +public class SupervisionOrderServiceIT extends AbstractIntegrationTestWithDatabase { + + protected SupervisionOrderService supervisionOrderService = + SupervisionOrderServiceFactory.getInstance().getSupervisionOrderService(); + + @Test + public void createSupervisionOrderTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + Item item = workspaceItem.getItem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + SupervisionOrder supervisionOrderOne = + supervisionOrderService.create(context, item, groupA); + + SupervisionOrder supervisionOrderTwo = + supervisionOrderService.create(context, item, groupB); + + context.restoreAuthSystemState(); + + assertThat(supervisionOrderOne, notNullValue()); + assertThat(supervisionOrderOne.getItem().getID(), is(item.getID())); + assertThat(supervisionOrderOne.getGroup().getID(), is(groupA.getID())); + + assertThat(supervisionOrderTwo, notNullValue()); + assertThat(supervisionOrderTwo.getItem().getID(), is(item.getID())); + assertThat(supervisionOrderTwo.getGroup().getID(), is(groupB.getID())); + + } + + @Test + public void findSupervisionOrderTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + SupervisionOrder supervisionOrderOne = + supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + + context.restoreAuthSystemState(); + + SupervisionOrder supervisionOrder = + supervisionOrderService.find(context, supervisionOrderOne.getID()); + + assertThat(supervisionOrder, notNullValue()); + assertThat(supervisionOrder.getID(), is(supervisionOrderOne.getID())); + + assertThat(supervisionOrder.getGroup().getID(), + is(supervisionOrderOne.getGroup().getID())); + + assertThat(supervisionOrder.getItem().getID(), + is(supervisionOrderOne.getItem().getID())); + + } + + @Test + public void findAllSupervisionOrdersTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + WorkspaceItem workspaceItemTwo = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item two") + .withIssueDate("2023-01-25") + .grantLicense() + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + supervisionOrderService.create(context, workspaceItem.getItem(), groupB); + supervisionOrderService.create(context, workspaceItemTwo.getItem(), groupA); + + context.restoreAuthSystemState(); + + assertThat(supervisionOrderService.findAll(context), hasSize(3)); + } + + @Test + public void findSupervisionOrderByItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + WorkspaceItem workspaceItemTwo = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item two") + .withIssueDate("2023-01-25") + .grantLicense() + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + supervisionOrderService.create(context, workspaceItem.getItem(), groupB); + supervisionOrderService.create(context, workspaceItemTwo.getItem(), groupA); + + context.restoreAuthSystemState(); + + assertThat(supervisionOrderService.findByItem(context, workspaceItem.getItem()), hasSize(2)); + assertThat(supervisionOrderService.findByItem(context, workspaceItemTwo.getItem()), hasSize(1)); + + } + + @Test + public void findSupervisionOrderByItemAndGroupTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + Item item = workspaceItem.getItem(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + supervisionOrderService.create(context, item, groupA); + + context.restoreAuthSystemState(); + + SupervisionOrder supervisionOrderA = + supervisionOrderService.findByItemAndGroup(context, item, groupA); + + assertThat(supervisionOrderA, notNullValue()); + assertThat(supervisionOrderA.getItem().getID(), is(item.getID())); + assertThat(supervisionOrderA.getGroup().getID(), is(groupA.getID())); + + // no supervision order on item and groupB + assertThat(supervisionOrderService.findByItemAndGroup(context, item, groupB), nullValue()); + + } + + @Test + public void isSupervisorTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("parent community") + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("collection") + .withEntityType("Publication") + .build(); + + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("workspace item") + .withIssueDate("2023-01-24") + .grantLicense() + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@example.org") + .build(); + + EPerson userB = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userB@example.org") + .build(); + + Group groupA = GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(userB) + .build(); + + supervisionOrderService.create(context, workspaceItem.getItem(), groupA); + + context.restoreAuthSystemState(); + + assertThat(supervisionOrderService.isSupervisor( + context, userA, workspaceItem.getItem()), is(true)); + + // userB is not a supervisor on workspace Item + assertThat(supervisionOrderService.isSupervisor( + context, userB, workspaceItem.getItem()), is(false)); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java index 8ac1e7fe063d..1cbfd5c63256 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java @@ -22,19 +22,13 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.exception.ResourceAlreadyExistsException; -import org.dspace.app.exception.ResourceConflictException; -import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.InvalidReCaptchaException; import org.dspace.orcid.exception.OrcidValidationException; import org.dspace.services.ConfigurationService; import org.springframework.beans.TypeMismatchException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.repository.support.QueryMethodParameterConversionException; import org.springframework.http.HttpHeaders; @@ -66,13 +60,6 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionHandler { private static final Logger log = LogManager.getLogger(); - @Autowired - @Lazy - private ConverterService converterService; - - @Autowired - private Utils utils; - /** * Default collection of HTTP error codes to log as ERROR with full stack trace. */ @@ -248,12 +235,6 @@ protected void handleGenericException(HttpServletRequest request, HttpServletRes } - @ExceptionHandler(ResourceConflictException.class) - protected ResponseEntity resourceConflictException(ResourceConflictException ex) { - RestModel resource = converterService.toRest(ex.getResource(), utils.obtainProjection()); - return new ResponseEntity(resource, HttpStatus.CONFLICT); - } - /** * Send the error to the response. * 5xx errors will be logged as ERROR with a full stack trace. 4xx errors diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java index 8cf73e8fe993..e253035be8f7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java @@ -19,7 +19,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.app.exception.ResourceConflictException; +import org.dspace.app.exception.ResourceAlreadyExistsException; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.ConverterService; @@ -116,6 +116,10 @@ public SupervisionOrderRest createAndReturn(Context context) throws AuthorizeExc throw new UnprocessableEntityException("Item with uuid: " + itemId + " not found"); } + if (item.isArchived()) { + throw new UnprocessableEntityException("An archived Item with uuid: " + itemId + " can't be supervised"); + } + Group group = groupService.find(context, UUID.fromString(groupId)); if (group == null) { throw new UnprocessableEntityException("Group with uuid: " + groupId + " not found"); @@ -123,10 +127,8 @@ public SupervisionOrderRest createAndReturn(Context context) throws AuthorizeExc supervisionOrder = supervisionOrderService.findByItemAndGroup(context, item, group); if (Objects.nonNull(supervisionOrder)) { - throw new ResourceConflictException( - "There is a conflict supervision order with itemId <" + itemId + "> and groupId <" + groupId + ">", - supervisionOrder - ); + throw new ResourceAlreadyExistsException( + "A supervision order already exists with itemId <" + itemId + "> and groupId <" + groupId + ">"); } supervisionOrder = supervisionOrderService.create(context, item, group); addGroupPoliciesToItem(context, item, group, type); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java index 2e3838abed19..96e0f9ac4de8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -7,7 +7,6 @@ */ package org.dspace.app.rest; import static com.jayway.jsonpath.JsonPath.read; -import static org.dspace.app.rest.matcher.ItemMatcher.matchItemWithTitleAndDateIssued; import static org.dspace.app.rest.matcher.SupervisionOrderMatcher.matchSuperVisionOrder; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -20,13 +19,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; -import org.dspace.app.rest.model.patch.AddOperation; +import org.dspace.app.rest.matcher.WorkspaceItemMatcher; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.repository.SupervisionOrderRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; @@ -40,6 +40,7 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.InstallItemService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.supervision.SupervisionOrder; @@ -59,6 +60,9 @@ public class SupervisionOrderRestRepositoryIT extends AbstractControllerIntegrat @Autowired private SupervisionOrderService supervisionOrderService; + @Autowired + private InstallItemService installItemService; + @Test public void findAllByAnonymousUserTest() throws Exception { getClient().perform(get("/api/core/supervisionorders/")) @@ -667,11 +671,15 @@ public void createTheSameSupervisionOrderTwiceTest() throws Exception { .withName("Collection 1") .build(); - Item itemOne = - ItemBuilder.createItem(context, col1) - .withTitle("item title") - .withIssueDate("2017-10-17") - .build(); + WorkspaceItem workspaceItem = + WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + Item itemOne = workspaceItem.getItem(); Group groupA = GroupBuilder.createGroup(context) @@ -695,7 +703,47 @@ public void createTheSameSupervisionOrderTwiceTest() throws Exception { .param("group", groupA.getID().toString()) .param("type", "OBSERVER") .contentType(contentType)) - .andExpect(status().isConflict()); + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void createSupervisionOrderOnArchivedItemTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + // create archived item + Item itemOne = + ItemBuilder.createItem(context, col1) + .withTitle("item title") + .withIssueDate("2017-10-17") + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(eperson) + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); } @Test @@ -722,16 +770,24 @@ public void createByAdminTest() throws Exception { .withName("Parent Community") .build(); - Collection col1 = + Collection publications = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1") + .withName("Publications") + .withEntityType("Publication") .build(); - Item itemOne = - ItemBuilder.createItem(context, col1) - .withTitle("item title") - .withIssueDate("2017-10-17") - .build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .withFulltext("simple-article.pdf", "/local/path/simple-article.pdf", pdf) + .grantLicense() + .build(); + + Item itemOne = witem.getItem(); Group groupA = GroupBuilder.createGroup(context) @@ -790,21 +846,38 @@ public void createByAdminTest() throws Exception { String authTokenB = getAuthToken(userB.getEmail(), password); String patchBody = getPatchContent(List.of( - new AddOperation("/metadata/dc.title", List.of(Map.of("value", "new title"))) + new ReplaceOperation("/sections/traditionalpageone/dc.title/0", Map.of("value", "New Title")) )); - // update title of itemOne by userA is Ok - getClient(authTokenA).perform(patch("/api/core/items/" + itemOne.getID()) + // update title of workspace item by userA is Ok + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", is( - matchItemWithTitleAndDateIssued(context.reloadEntity(itemOne), - "new title", "2017-10-17") - ))); + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + // check the new title and untouched values + Matchers.is(WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry")))); + + getClient(authTokenA).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + Matchers.is( + WorkspaceItemMatcher + .matchItemWithTitleAndDateIssuedAndSubject( + witem, + "New Title", "2017-10-17", + "ExtraEntry") + ))); - // update title of itemOne by userB is Forbidden - getClient(authTokenB).perform(patch("/api/core/items/" + itemOne.getID()) + + // update title of workspace item by userB is Forbidden + getClient(authTokenB).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isForbidden()); @@ -1085,4 +1158,89 @@ public void deleteWorkspaceItemThenSupervisionOrderBeDeletedTest() throws Except } + @Test + public void installWorkspaceItemThenSupervisionOrderBeDeletedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test1@email.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + context.restoreAuthSystemState(); + + AtomicReference idRefOne = new AtomicReference<>(); + AtomicReference idRefTwo = new AtomicReference<>(); + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRefOne + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRefTwo + .set(read(result.getResponse().getContentAsString(), "$.id"))); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRefOne.get())) + .andExpect(status().isOk()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRefTwo.get())) + .andExpect(status().isOk()); + + // install item then supervision orders will be deleted + context.turnOffAuthorisationSystem(); + installItemService.installItem(context, context.reloadEntity(witem)); + context.restoreAuthSystemState(); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRefOne.get())) + .andExpect(status().isNotFound()); + + getClient(adminToken).perform(get("/api/core/supervisionorders/" + idRefTwo.get())) + .andExpect(status().isNotFound()); + + } + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index 0bf9cd5297c8..ac743b2e5a09 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -2126,70 +2126,141 @@ public void whenWorkspaceitemBecomeWorkflowitemWithAccessConditionsTheItemMustBe } @Test - public void testSupervisorFindOne() throws Exception { + public void createSupervisionOnWorkspaceThenSubmitToBeWorkflowTest() throws Exception { context.turnOffAuthorisationSystem(); - EPerson user = - EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withEmail("user@test.com") - .withPassword(password) - .build(); - - EPerson anotherUser = - EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withEmail("anotheruser@test.com") - .withPassword(password) - .build(); - parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); - Community child1 = - CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); + EPerson reviewer = + EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); Collection collection = - CollectionBuilder.createCollection(context, child1) + CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") - .withWorkflowGroup(1, admin) + .withWorkflowGroup("reviewer", reviewer) .build(); - Group group = + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + Group groupA = GroupBuilder.createGroup(context) .withName("group A") - .addMember(user) + .addMember(userA) .build(); - XmlWorkflowItem workflowItem = - WorkflowItemBuilder.createWorkflowItem(context, collection) - .withSubmitter(admin) - .withTitle("Workflow Item") - .withIssueDate("2017-10-17") - .withAuthor("Author one") - .build(); + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .withFulltext("simple-article.pdf", "/local/path/simple-article.pdf", pdf) + .grantLicense() + .build(); context.restoreAuthSystemState(); + // create a supervision order on workspaceItem to groupA getClient(getAuthToken(admin.getEmail(), password)) .perform(post("/api/core/supervisionorders/") - .param("uuid", workflowItem.getItem().getID().toString()) - .param("group", group.getID().toString()) + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) .param("type", "EDITOR") .contentType(contentType)) .andExpect(status().isCreated()); - getClient(getAuthToken(anotherUser.getEmail(), password)) - .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) - .andExpect(status().isForbidden()); + String authTokenA = getAuthToken(userA.getEmail(), password); + + // a simple patch to update an existent metadata + String patchBody = + getPatchContent(List.of( + new ReplaceOperation("/sections/traditionalpageone/dc.title/0", Map.of("value", "New Title")) + )); + + // supervisor update the title of the workspaceItem + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + // check the new title and untouched values + Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "New Title", "2017-10-17", "ExtraEntry" + )))); + + // supervisor check that title has been updated + getClient(authTokenA).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "New Title", "2017-10-17", "ExtraEntry" + )))); + + AtomicReference idRef = new AtomicReference(); + try { + String adminToken = getAuthToken(admin.getEmail(), password); + String reviewerToken = getAuthToken(reviewer.getEmail(), password); + + // submit the workspaceitem to start the workflow + getClient(adminToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + witem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()) + .andDo(result -> + idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // supervisor can read the workflowitem + getClient(authTokenA) + .perform(get("/api/workflow/workflowitems/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "New Title", "2017-10-17")))) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))); - getClient(getAuthToken(user.getEmail(), password)) - .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) - .andExpect(status().isOk()); + // reviewer can read the workflowitem + getClient(reviewerToken) + .perform(get("/api/workflow/workflowitems/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "New Title", "2017-10-17")))) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))); + + // a simple patch to update an existent metadata + String patchBodyTwo = + getPatchContent(List.of(new ReplaceOperation("/metadata/dc.title", Map.of("value", "edited title")))); + + // supervisor can't edit item in workflow + getClient(authTokenA).perform(patch("/api/core/items/" + witem.getItem().getID()) + .content(patchBodyTwo) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // supervisor can't edit the workflow item + getClient(authTokenA).perform(patch("/api/workflow/workflowitems/" + idRef.get()) + .content(patchBody) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } finally { + if (idRef.get() != null) { + WorkflowItemBuilder.deleteWorkflowItem(idRef.get()); + } + } } } diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index f7ddd90bc6e7..78b9f724599b 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -48,7 +48,6 @@ - @@ -101,7 +100,6 @@ - diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 586ed30e1ec8..a10b7149ff08 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -64,7 +64,7 @@ - + @@ -902,10 +902,10 @@ - - + From 6a02994c5beb1da5ba5663869e78a78f44ebd688 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 1 Feb 2023 17:13:42 +0100 Subject: [PATCH 209/394] [CST-7756] refactoring --- ...scriptionEmailNotificationServiceImpl.java | 66 +++++++++---------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java index d9937143cb24..bb507edd6537 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -7,23 +7,23 @@ */ package org.dspace.subscriptions; +import static org.dspace.core.Constants.COLLECTION; +import static org.dspace.core.Constants.COMMUNITY; +import static org.dspace.core.Constants.ITEM; + import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.dspace.content.Collection; -import org.dspace.content.Community; import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; import org.dspace.eperson.Subscription; @@ -32,8 +32,6 @@ import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.dspace.subscriptions.service.DSpaceObjectUpdates; import org.dspace.subscriptions.service.SubscriptionGenerator; -import org.hibernate.proxy.HibernateProxy; -import org.hibernate.proxy.LazyInitializer; import org.springframework.beans.factory.annotation.Autowired; /** @@ -55,29 +53,24 @@ public class SubscriptionEmailNotificationServiceImpl implements SubscriptionEma private Map contentUpdates = new HashMap<>(); @SuppressWarnings("rawtypes") private Map subscriptionType2generators = new HashMap<>(); - @SuppressWarnings("rawtypes") - private List communities = new ArrayList<>(); - @SuppressWarnings("rawtypes") - private List collections = new ArrayList<>(); - @SuppressWarnings("rawtypes") - private List items = new ArrayList<>(); @Autowired private SubscribeService subscribeService; @SuppressWarnings("rawtypes") - public SubscriptionEmailNotificationServiceImpl(SubscribeService subscribeService, - Map> param2values, + public SubscriptionEmailNotificationServiceImpl(Map> param2values, Map contentUpdates, Map subscriptionType2generators) { this.param2values = param2values; this.contentUpdates = contentUpdates; - this.subscribeService = subscribeService; this.subscriptionType2generators = subscriptionType2generators; } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "rawtypes", "unchecked" }) public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency) { + List items = new ArrayList<>(); + List communities = new ArrayList<>(); + List collections = new ArrayList<>(); try { List subscriptions = findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, frequency); @@ -86,19 +79,21 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc // the list of the person who has subscribed int iterator = 0; for (Subscription subscription : subscriptions) { - DSpaceObject dSpaceObject = getdSpaceObject(subscription); - if (dSpaceObject instanceof Community) { - communities.addAll(contentUpdates.get(Community.class.getSimpleName().toLowerCase(Locale.ROOT)) - .findUpdates(context, dSpaceObject, frequency)); - } else if (dSpaceObject instanceof Collection) { - collections.addAll(contentUpdates.get(Collection.class.getSimpleName().toLowerCase(Locale.ROOT)) - .findUpdates(context, dSpaceObject, frequency)); - } else if (dSpaceObject instanceof Item) { - items.addAll(contentUpdates.get(Item.class.getSimpleName().toLowerCase(Locale.ROOT)) - .findUpdates(context, dSpaceObject, frequency)); + DSpaceObject dSpaceObject = subscription.getDSpaceObject(); + + if (dSpaceObject.getType() == COMMUNITY) { + communities.addAll(contentUpdates.get("community") + .findUpdates(context, dSpaceObject, frequency)); + } else if (dSpaceObject.getType() == COLLECTION) { + collections.addAll(contentUpdates.get("collection") + .findUpdates(context, dSpaceObject, frequency)); + } else if (dSpaceObject.getType() == ITEM) { + items.addAll(contentUpdates.get("item").findUpdates(context, dSpaceObject, frequency)); } + var ePerson = subscription.getEPerson(); if (iterator < subscriptions.size() - 1) { + // as the subscriptions are ordered by eperson id, so we send them by ePerson if (ePerson.equals(subscriptions.get(iterator + 1).getEPerson())) { iterator++; continue; @@ -127,16 +122,15 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc } } - private DSpaceObject getdSpaceObject(Subscription subscription) { - DSpaceObject dSpaceObject = subscription.getDSpaceObject(); - if (subscription.getDSpaceObject() instanceof HibernateProxy) { - HibernateProxy hibernateProxy = (HibernateProxy) subscription.getDSpaceObject(); - LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); - dSpaceObject = (DSpaceObject) initializer.getImplementation(); - } - return dSpaceObject; - } - + /** + * Return all Subscriptions by subscriptionType and frequency ordered by ePerson ID + * if there are none it returns an empty list + * + * @param context DSpace context + * @param subscriptionType Could be "content" or "statistics". NOTE: in DSpace we have only "content" + * @param frequency Could be "D" stand for Day, "W" stand for Week, and "M" stand for Month + * @return + */ private List findAllSubscriptionsBySubscriptionTypeAndFrequency(Context context, String subscriptionType, String frequency) { try { From 7267e151cf9715b53c2b7a8a9b692e5c0aa186f1 Mon Sep 17 00:00:00 2001 From: Toni Prieto Date: Wed, 1 Feb 2023 17:25:42 +0100 Subject: [PATCH 210/394] Remove statusFilter added to Solr browse indexes queries to solve an issue with the local administrators navigation --- .../java/org/dspace/browse/SolrBrowseDAO.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java index 860137194daf..e02367f6eb8e 100644 --- a/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java +++ b/dspace-api/src/main/java/org/dspace/browse/SolrBrowseDAO.java @@ -8,7 +8,6 @@ package org.dspace.browse; import java.io.Serializable; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -179,7 +178,6 @@ private DiscoverResult getSolrResponse() throws BrowseException { DiscoverQuery query = new DiscoverQuery(); addLocationScopeFilter(query); addDefaultFilterQueries(query); - addStatusFilter(query); if (distinct) { DiscoverFacetField dff; if (StringUtils.isNotBlank(startsWith)) { @@ -230,18 +228,6 @@ private DiscoverResult getSolrResponse() throws BrowseException { return sResponse; } - private void addStatusFilter(DiscoverQuery query) { - try { - if (!authorizeService.isAdmin(context) - && (authorizeService.isCommunityAdmin(context) - || authorizeService.isCollectionAdmin(context))) { - query.addFilterQueries(searcher.createLocationQueryForAdministrableItems(context)); - } - } catch (SQLException ex) { - log.error("Error looking up authorization rights of current user", ex); - } - } - private void addLocationScopeFilter(DiscoverQuery query) { if (container != null) { if (containerIDField.startsWith("collection")) { @@ -346,7 +332,6 @@ public int doOffsetQuery(String column, String value, boolean isAscending) DiscoverQuery query = new DiscoverQuery(); addLocationScopeFilter(query); addDefaultFilterQueries(query); - addStatusFilter(query); query.setMaxResults(0); query.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE); From de7d1738fc70c0da108de12f6568413d52c328ec Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 2 Feb 2023 12:53:57 +0100 Subject: [PATCH 211/394] [CST-7756] monor fix --- .../org/dspace/eperson/SubscribeServiceImpl.java | 10 +++++++--- .../java/org/dspace/eperson/Subscription.java | 4 ---- .../SubscriptionEmailNotificationServiceImpl.java | 10 +++++++--- .../SubscriptionDSpaceObjectLinkRepository.java | 15 +-------------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index b92f1138d891..3aae28af4982 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -17,7 +17,9 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; +import org.dspace.content.Community; import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -49,7 +51,9 @@ public List findAll(Context context, String resourceType, Integer if (StringUtils.isBlank(resourceType)) { return subscriptionDAO.findAllOrderedByDSO(context, limit, offset); } else { - if (resourceType.equals("Item") || resourceType.equals("Collection") || resourceType.equals("Community")) { + if (resourceType.equals(Item.class.getSimpleName()) || + resourceType.equals(Collection.class.getSimpleName()) || + resourceType.equals(Community.class.getSimpleName())) { return subscriptionDAO.findAllOrderedByIDAndResourceType(context, resourceType, limit, offset); } else { log.error("Resource type must be Item, Collection or Community"); @@ -71,7 +75,7 @@ public Subscription subscribe(Context context, EPerson eperson, subscriptionParameterList.forEach(subscriptionParameter -> newSubscription.addParameter(subscriptionParameter)); newSubscription.setEPerson(eperson); - newSubscription.setdSpaceObject(dSpaceObject); + newSubscription.setDSpaceObject(dSpaceObject); newSubscription.setSubscriptionType(type); return newSubscription; } else { @@ -154,7 +158,7 @@ public Subscription updateSubscription(Context context, Integer id, EPerson eper Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); subscriptionDB.removeParameterList(); subscriptionDB.setSubscriptionType(type); - subscriptionDB.setdSpaceObject(dSpaceObject); + subscriptionDB.setDSpaceObject(dSpaceObject); subscriptionParameterList.forEach(x -> subscriptionDB.addParameter(x)); subscriptionDB.setEPerson(eperson); subscriptionDAO.save(context, subscriptionDB); diff --git a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java index 9856e03e0e0e..5db63740f477 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Subscription.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Subscription.java @@ -87,10 +87,6 @@ public void setEPerson(EPerson ePerson) { this.ePerson = ePerson; } - public void setdSpaceObject(DSpaceObject dSpaceObject) { - this.dSpaceObject = dSpaceObject; - } - public String getSubscriptionType() { return subscriptionType; } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java index bb507edd6537..4dd618cc69e8 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -23,7 +23,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.content.Collection; +import org.dspace.content.Community; import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; import org.dspace.eperson.Subscription; @@ -82,13 +85,14 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc DSpaceObject dSpaceObject = subscription.getDSpaceObject(); if (dSpaceObject.getType() == COMMUNITY) { - communities.addAll(contentUpdates.get("community") + communities.addAll(contentUpdates.get(Community.class.getSimpleName()) .findUpdates(context, dSpaceObject, frequency)); } else if (dSpaceObject.getType() == COLLECTION) { - collections.addAll(contentUpdates.get("collection") + collections.addAll(contentUpdates.get(Collection.class.getSimpleName()) .findUpdates(context, dSpaceObject, frequency)); } else if (dSpaceObject.getType() == ITEM) { - items.addAll(contentUpdates.get("item").findUpdates(context, dSpaceObject, frequency)); + items.addAll(contentUpdates.get(Item.class.getSimpleName()) + .findUpdates(context, dSpaceObject, frequency)); } var ePerson = subscription.getEPerson(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java index dd9db2aea660..95c4714e9cae 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionDSpaceObjectLinkRepository.java @@ -15,13 +15,8 @@ import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.projection.Projection; -import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.Item; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.SubscribeService; -import org.hibernate.proxy.HibernateProxy; -import org.hibernate.proxy.LazyInitializer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; @@ -45,15 +40,7 @@ public DSpaceObjectRest getDSpaceObject(@Nullable HttpServletRequest request, In if (Objects.isNull(subscription)) { throw new ResourceNotFoundException("No such subscription: " + subscriptionId); } - if (subscription.getDSpaceObject() instanceof Item || - subscription.getDSpaceObject() instanceof Community || - subscription.getDSpaceObject() instanceof Collection) { - return converter.toRest(subscription.getDSpaceObject(), projection); - } else { - HibernateProxy hibernateProxy = (HibernateProxy) subscription.getDSpaceObject(); - LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer(); - return converter.toRest(initializer.getImplementation(), projection); - } + return converter.toRest(subscription.getDSpaceObject(), projection); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } From 0d872b543ceb0b1f4f2d7e11f5b430ac295cec04 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 2 Feb 2023 15:39:24 +0100 Subject: [PATCH 212/394] [CST-7756] added unit tests covering methods with business logic --- .../dspace/eperson/SubscribeServiceTest.java | 396 ++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java diff --git a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java new file mode 100644 index 000000000000..3105d183199a --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java @@ -0,0 +1,396 @@ +/* + * 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.eperson; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.eperson.dao.SubscriptionDAO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class SubscribeServiceTest { + + @Mock + private SubscriptionDAO subscriptionDAO; + + @Mock + private AuthorizeService authorizeService; + + @InjectMocks + private SubscribeServiceImpl subscribeService; + + private final Context context = Mockito.mock(Context.class); + + @Test + public void findAllWithValidResource() throws Exception { + + String resourceType = "Item"; + Integer limit = 10; + Integer offset = 0; + + Subscription subscription = createSubscription("content", + getSubscriptionParameter("frequency", "W")); + + when(subscriptionDAO.findAllOrderedByIDAndResourceType(context, resourceType, limit, offset)) + .thenReturn(Collections.singletonList(subscription)); + + List subscriptions = subscribeService.findAll(context, resourceType, limit, offset); + + assertEquals(subscriptions, Collections.singletonList(subscription)); + + } + + @Test + public void findAllWithoutResourceType() throws Exception { + String resourceType = StringUtils.EMPTY; + Integer limit = 10; + Integer offset = 0; + + Subscription subscription = createSubscription("content", + getSubscriptionParameter("frequency", "W")); + + when(subscriptionDAO.findAllOrderedByDSO(context, limit, offset)) + .thenReturn(Collections.singletonList(subscription)); + + List subscriptions = subscribeService.findAll(context, resourceType, limit, offset); + + assertEquals(subscriptions, Collections.singletonList(subscription)); + } + + @Test(expected = Exception.class) + public void findAllWithInValidResource() throws Exception { + + String resourceType = "INVALID"; + Integer limit = 10; + Integer offset = 0; + + subscribeService.findAll(context, resourceType, limit, offset); + + verifyNoInteractions(subscriptionDAO); + + } + + @Test + public void newSubscriptionCreatedByAdmin() throws Exception { + + Subscription dbSubscription = createSubscription("content", + getSubscriptionParameter("frequency", "W")); + + when(authorizeService.isAdmin(context)).thenReturn(true); + when(subscriptionDAO.create(eq(context), any(Subscription.class))) + .thenReturn(dbSubscription); + DSpaceObject dso = mock(DSpaceObject.class); + + List parameters = Collections.singletonList( + getSubscriptionParameter("frequency", "M")); + + Subscription subscription = subscribeService.subscribe(context, getePerson(), + dso, parameters, "content"); + + assertEquals(subscription, dbSubscription); + } + + @Test + public void newSubscriptionCreatedByCurrentUser() throws Exception { + + Subscription dbSubscription = createSubscription("content", + getSubscriptionParameter("frequency", "W")); + EPerson currentUser = getePerson(); + + when(context.getCurrentUser()).thenReturn(currentUser); + + when(subscriptionDAO.create(eq(context), any(Subscription.class))) + .thenReturn(dbSubscription); + DSpaceObject dso = mock(DSpaceObject.class); + + List parameters = Collections.singletonList( + getSubscriptionParameter("frequency", "D")); + + Subscription subscription = subscribeService.subscribe(context, currentUser, + dso, parameters, "content"); + + assertEquals(subscription, dbSubscription); + } + + @Test(expected = AuthorizeException.class) + public void nonAdminDifferentUserTriesToSubscribe() throws Exception { + + EPerson currentUser = getePerson(); + EPerson subscribingUser = getePerson(); + + when(context.getCurrentUser()).thenReturn(currentUser); + + DSpaceObject dso = mock(DSpaceObject.class); + + List parameters = Collections.singletonList( + getSubscriptionParameter("frequency", "D")); + + subscribeService.subscribe(context, subscribingUser,dso, parameters, "content"); + + verifyNoInteractions(subscriptionDAO); + } + + @Test(expected = SQLException.class) + public void exceptionWhileStoringSubscription() throws Exception { + EPerson currentUser = getePerson(); + + when(context.getCurrentUser()).thenReturn(currentUser); + + doThrow(new SQLException()).when(subscriptionDAO).create(eq(context), any(Subscription.class)); + + DSpaceObject dso = mock(DSpaceObject.class); + + List parameters = Collections.singletonList( + getSubscriptionParameter("frequency", "D")); + + subscribeService.subscribe(context, currentUser,dso, parameters, "content"); + + } + + @Test + public void unsubscribeByAdmin() throws Exception { + + when(authorizeService.isAdmin(context)).thenReturn(true); + DSpaceObject dso = mock(DSpaceObject.class); + + EPerson currentUser = getePerson(); + subscribeService.unsubscribe(context, currentUser, + dso); + verify(subscriptionDAO).deleteByDSOAndEPerson(context, dso, currentUser); + } + + @Test + public void unsubscribeByCurrentUser() throws Exception { + + EPerson currentUser = getePerson(); + when(authorizeService.isAdmin(context)).thenReturn(false); + when(context.getCurrentUser()).thenReturn(currentUser); + + DSpaceObject dso = mock(DSpaceObject.class); + + subscribeService.unsubscribe(context, currentUser, dso); + verify(subscriptionDAO).deleteByDSOAndEPerson(context, dso, currentUser); + } + + @Test(expected = AuthorizeException.class) + public void nonAdminDifferentUserTriesToUnSubscribe() throws Exception { + EPerson currentUser = getePerson(); + EPerson unsubscribingUser = getePerson(); + + when(authorizeService.isAdmin(context)).thenReturn(false); + when(context.getCurrentUser()).thenReturn(currentUser); + + DSpaceObject dso = mock(DSpaceObject.class); + + subscribeService.unsubscribe(context, unsubscribingUser, dso); + + verifyNoInteractions(subscriptionDAO); + } + + @Test(expected = SQLException.class) + public void exceptionWhileDeletingSubscription() throws Exception { + EPerson currentUser = getePerson(); + + when(context.getCurrentUser()).thenReturn(currentUser); + + DSpaceObject dso = mock(DSpaceObject.class); + + doThrow(new SQLException()).when(subscriptionDAO).deleteByDSOAndEPerson(eq(context), eq(dso), + eq(currentUser)); + + subscribeService.unsubscribe(context, currentUser,dso); + + } + + @Test + public void updateSubscription() throws Exception { + + + int subscriptionId = 434123; + + Subscription subscription = createSubscription("original", getSubscriptionParameter("frequency", "D"), + getSubscriptionParameter("frequency", "M")); + + when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) + .thenReturn(subscription); + + String updatedType = "updated"; + List updatedParameters = Collections.singletonList( + getSubscriptionParameter("frequency", "A") + ); + + Subscription updated = subscribeService.updateSubscription(context, subscriptionId, null, null, + updatedParameters, updatedType); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Subscription.class); + verify(subscriptionDAO).save(eq(context), captor.capture()); + + assertEquals(captor.getValue().getSubscriptionParameterList(), updatedParameters); + assertEquals(captor.getValue().getSubscriptionType(), updatedType); + + assertEquals(updated.getSubscriptionType(), updatedType); + assertEquals(updated.getSubscriptionParameterList(), updatedParameters); + + } + + @Test(expected = SQLException.class) + public void errorWhileUpdating() throws Exception { + + + int subscriptionId = 434; + + Subscription subscription = createSubscription("original", getSubscriptionParameter("frequency", "D"), + getSubscriptionParameter("frequency", "M")); + + when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) + .thenReturn(subscription); + doThrow(new SQLException()).when(subscriptionDAO).save(eq(context), any(Subscription.class)); + + String updatedType = "updated"; + List updatedParameters = Collections.singletonList( + getSubscriptionParameter("frequency", "A") + ); + + subscribeService.updateSubscription(context, subscriptionId, null, null, + updatedParameters, updatedType); + + } + + @Test + public void parametersAdditionAndRemoval() throws Exception { + + int subscriptionId = 43123; + + SubscriptionParameter firstParameter = getSubscriptionParameter("key1", "value1"); + SubscriptionParameter secondParameter = getSubscriptionParameter("key2", "value2"); + + Subscription existingSubscription = createSubscription("type", + firstParameter, secondParameter); + + when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) + .thenReturn(existingSubscription); + + SubscriptionParameter addedParameter = getSubscriptionParameter("added", "add"); + + Subscription updatedSubscription = subscribeService.addSubscriptionParameter(context, subscriptionId, + addedParameter); + + assertParametersAreUpdated(updatedSubscription, List.of(firstParameter, secondParameter, + addedParameter)); + + updatedSubscription = subscribeService.removeSubscriptionParameter(context, subscriptionId, + secondParameter); + + assertParametersAreUpdated(updatedSubscription, List.of(firstParameter, addedParameter)); + + + } + + @Test(expected = SQLException.class) + public void exceptionWhileAddingParameters() throws Exception { + int subscriptionId = 43123; + + SubscriptionParameter firstParameter = getSubscriptionParameter("key1", "value1"); + SubscriptionParameter secondParameter = getSubscriptionParameter("key2", "value2"); + + Subscription existingSubscription = createSubscription("type", + firstParameter, secondParameter); + + when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) + .thenReturn(existingSubscription); + + + doThrow(new SQLException()).when(subscriptionDAO).save(eq(context), any(Subscription.class)); + + subscribeService.addSubscriptionParameter(context, subscriptionId, + getSubscriptionParameter("added", "add")); + } + + @Test(expected = SQLException.class) + public void exceptionWhileRemovingParameters() throws Exception { + int subscriptionId = 43127; + + SubscriptionParameter firstParameter = getSubscriptionParameter("key1", "value1"); + SubscriptionParameter secondParameter = getSubscriptionParameter("key2", "value2"); + + Subscription existingSubscription = createSubscription("type", + firstParameter, secondParameter); + + when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) + .thenReturn(existingSubscription); + + + doThrow(new SQLException()).when(subscriptionDAO).save(eq(context), any(Subscription.class)); + + subscribeService.removeSubscriptionParameter(context, subscriptionId, + secondParameter); + } + + private static Subscription createSubscription(String type, SubscriptionParameter... parameters) { + Subscription subscription = new Subscription(); + subscription.setSubscriptionType(type); + + subscription.setSubscriptionParameterList + (new ArrayList<>(Arrays.asList(parameters))); + return subscription; + } + + private void assertParametersAreUpdated(Subscription updatedSubscription, + List expectedParameters) + throws SQLException { + ArgumentCaptor captor = ArgumentCaptor.forClass(Subscription.class); + + verify(subscriptionDAO, atLeastOnce()).save(eq(context), captor.capture()); + + + assertEquals(captor.getValue().getSubscriptionParameterList(), expectedParameters); + assertEquals(updatedSubscription.getSubscriptionParameterList(), expectedParameters); + } + + private static SubscriptionParameter getSubscriptionParameter(String name, String value) { + SubscriptionParameter parameter = new SubscriptionParameter(); + parameter.setName(name); + parameter.setValue(value); + return parameter; + } + + private static EPerson getePerson() { + EPerson ePerson = mock(EPerson.class); + UUID uuid = UUID.randomUUID(); + doReturn(uuid).when(ePerson).getID(); + return ePerson; + } +} \ No newline at end of file From f0acbb957559702ca200699db8bdd31bf08e720f Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 2 Feb 2023 15:51:24 +0100 Subject: [PATCH 213/394] [CST-7756] sub-daily command and its referred class removed, as subscriptions are sent by subscription-send command --- .../org/dspace/eperson/SubscribeCLITool.java | 439 ------------------ dspace/config/launcher.xml | 7 - 2 files changed, 446 deletions(-) delete mode 100644 dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java deleted file mode 100644 index dd0bd8287bd3..000000000000 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java +++ /dev/null @@ -1,439 +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.eperson; - -import java.io.IOException; -import java.sql.SQLException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.ResourceBundle; -import java.util.TimeZone; -import javax.mail.MessagingException; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.dspace.content.Collection; -import org.dspace.content.DCDate; -import org.dspace.content.Item; -import org.dspace.content.MetadataSchemaEnum; -import org.dspace.content.MetadataValue; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.core.Email; -import org.dspace.core.I18nUtil; -import org.dspace.core.LogHelper; -import org.dspace.eperson.factory.EPersonServiceFactory; -import org.dspace.eperson.service.SubscribeService; -import org.dspace.handle.factory.HandleServiceFactory; -import org.dspace.handle.service.HandleService; -import org.dspace.search.Harvest; -import org.dspace.search.HarvestedItemInfo; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; - -/** - * CLI tool used for sending new item e-mail alerts to users - * - * @author Robert Tansley - * @version $Revision$ - */ -public class SubscribeCLITool { - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SubscribeCLITool.class); - - private static final HandleService handleService - = HandleServiceFactory.getInstance().getHandleService(); - private static final ItemService itemService - = ContentServiceFactory.getInstance().getItemService(); - private static final SubscribeService subscribeService - = EPersonServiceFactory.getInstance().getSubscribeService(); - private static final ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); - - /** - * Default constructor - */ - private SubscribeCLITool() { } - - /** - * Process subscriptions. This must be invoked only once a day. Messages are - * only sent out when a collection has actually received new items, so that - * people's mailboxes are not clogged with many "no new items" mails. - *

- * Yesterday's newly available items are included. If this is run at for - * example midday, any items that have been made available during the - * current day will not be included, but will be included in the next day's - * run. - *

- * For example, if today's date is 2002-10-10 (in UTC) items made available - * during 2002-10-09 (UTC) will be included. - * - * @param context The relevant DSpace Context. - * @param test If true, do a "dry run", i.e. don't actually send email, just log the attempt - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. - */ - public static void processDaily(Context context, boolean test) throws SQLException, - IOException { - // Grab the subscriptions - - List subscriptions = new ArrayList<>(); - try { - subscriptions = subscribeService.findAll(context, null, -1, -1); - } catch (Exception e) { - log.error(e.getMessage()); - } - EPerson currentEPerson = null; - List collections = null; // List of Collections - - // Go through the list collating subscriptions for each e-person - for (Subscription subscription : subscriptions) { - if (!(subscription.getDSpaceObject() != null && subscription.getDSpaceObject() instanceof Collection)) { - continue; - } - // Does this row relate to the same e-person as the last? - if ((currentEPerson == null) - || (!subscription.getEPerson().getID().equals(currentEPerson - .getID()))) { - // New e-person. Send mail for previous e-person - if (currentEPerson != null) { - - try { - sendEmail(context, currentEPerson, collections, test); - } catch (MessagingException me) { - log.error("Failed to send subscription to eperson_id=" - + currentEPerson.getID()); - log.error(me); - } - } - - currentEPerson = subscription.getEPerson(); - collections = new ArrayList<>(); - } - - collections.add((Collection) subscription.getDSpaceObject()); - } - - // Process the last person - if (currentEPerson != null) { - try { - sendEmail(context, currentEPerson, collections, test); - } catch (MessagingException me) { - log.error("Failed to send subscription to eperson_id=" - + currentEPerson.getID()); - log.error(me); - } - } - } - - /** - * Sends an email to the given e-person with details of new items in the - * given collections, items that appeared yesterday. No e-mail is sent if - * there aren't any new items in any of the collections. - * - * @param context DSpace context object - * @param eperson eperson to send to - * @param collections List of collection IDs (Integers) - * @param test If true, do a "dry run", i.e. don't actually send email, just log the attempt - * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. - * @throws MessagingException A general class of exceptions for sending email. - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - public static void sendEmail(Context context, EPerson eperson, - List collections, boolean test) throws IOException, MessagingException, - SQLException { - // Get a resource bundle according to the eperson language preferences - Locale supportedLocale = I18nUtil.getEPersonLocale(eperson); - ResourceBundle labels = ResourceBundle.getBundle("Messages", supportedLocale); - - // Get the start and end dates for yesterday - - // The date should reflect the timezone as well. Otherwise we stand to lose that information - // in truncation and roll to an earlier date than intended. - Calendar cal = Calendar.getInstance(TimeZone.getDefault()); - cal.setTime(new Date()); - - // What we actually want to pass to Harvest is "Midnight of yesterday in my current timezone" - // Truncation will actually pass in "Midnight of yesterday in UTC", which will be, - // at least in CDT, "7pm, the day before yesterday, in my current timezone". - cal.add(Calendar.HOUR, -24); - cal.set(Calendar.HOUR_OF_DAY, 0); - cal.set(Calendar.MINUTE, 0); - cal.set(Calendar.SECOND, 0); - Date midnightYesterday = cal.getTime(); - - - // FIXME: text of email should be more configurable from an - // i18n viewpoint - StringBuilder emailText = new StringBuilder(); - boolean isFirst = true; - - for (int i = 0; i < collections.size(); i++) { - Collection c = collections.get(i); - - try { - boolean includeAll = configurationService - .getBooleanProperty("harvest.includerestricted.subscription", true); - - // we harvest all the changed item from yesterday until now - List itemInfos = Harvest - .harvest(context, c, new DCDate(midnightYesterday).toString(), null, 0, // Limit - // and - // offset - // zero, - // get - // everything - 0, true, // Need item objects - false, // But not containers - false, // Or withdrawals - includeAll); - - if (configurationService.getBooleanProperty("eperson.subscription.onlynew", false)) { - // get only the items archived yesterday - itemInfos = filterOutModified(itemInfos); - } else { - // strip out the item archived today or - // not archived yesterday and modified today - itemInfos = filterOutToday(itemInfos); - } - - // Only add to buffer if there are new items - if (itemInfos.size() > 0) { - if (!isFirst) { - emailText - .append("\n---------------------------------------\n"); - } else { - isFirst = false; - } - - emailText.append(labels.getString("org.dspace.eperson.Subscribe.new-items")).append(" ").append( - c.getName()).append(": ").append( - itemInfos.size()).append("\n\n"); - - for (int j = 0; j < itemInfos.size(); j++) { - HarvestedItemInfo hii = (HarvestedItemInfo) itemInfos - .get(j); - - String title = hii.item.getName(); - emailText.append(" ").append(labels.getString("org.dspace.eperson.Subscribe.title")) - .append(" "); - - if (StringUtils.isNotBlank(title)) { - emailText.append(title); - } else { - emailText.append(labels.getString("org.dspace.eperson.Subscribe.untitled")); - } - - List authors = itemService - .getMetadata(hii.item, MetadataSchemaEnum.DC.getName(), "contributor", Item.ANY, Item.ANY); - - if (authors.size() > 0) { - emailText.append("\n ").append(labels.getString("org.dspace.eperson.Subscribe.authors")) - .append(" ").append( - authors.get(0).getValue()); - - for (int k = 1; k < authors.size(); k++) { - emailText.append("\n ").append( - authors.get(k).getValue()); - } - } - - emailText.append("\n ").append(labels.getString("org.dspace.eperson.Subscribe.id")) - .append(" ").append( - handleService.getCanonicalForm(hii.handle)).append( - "\n\n"); - } - } - } catch (ParseException pe) { - // This should never get thrown as the Dates are auto-generated - } - } - - // Send an e-mail if there were any new items - if (emailText.length() > 0) { - - if (test) { - log.info(LogHelper.getHeader(context, "subscription:", "eperson=" + eperson.getEmail())); - log.info(LogHelper.getHeader(context, "subscription:", "text=" + emailText.toString())); - - } else { - - Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscription")); - email.addRecipient(eperson.getEmail()); - email.addArgument(emailText.toString()); - email.send(); - - log.info(LogHelper.getHeader(context, "sent_subscription", "eperson_id=" + eperson.getID())); - - } - - - } - } - - /** - * Method for invoking subscriptions via the command line - * - * @param argv the command line arguments given - */ - public static void main(String[] argv) { - String usage = "org.dspace.eperson.Subscribe [-t] or nothing to send out subscriptions."; - - Options options = new Options(); - HelpFormatter formatter = new HelpFormatter(); - CommandLine line = null; - - { - Option opt = new Option("t", "test", false, "Run test session"); - opt.setRequired(false); - options.addOption(opt); - } - - { - Option opt = new Option("h", "help", false, "Print this help message"); - opt.setRequired(false); - options.addOption(opt); - } - - try { - line = new DefaultParser().parse(options, argv); - } catch (org.apache.commons.cli.ParseException e) { - // automatically generate the help statement - formatter.printHelp(usage, e.getMessage(), options, ""); - System.exit(1); - } - - if (line.hasOption("h")) { - // automatically generate the help statement - formatter.printHelp(usage, options); - System.exit(1); - } - - boolean test = line.hasOption("t"); - - Context context = null; - - try { - context = new Context(Context.Mode.READ_ONLY); - processDaily(context, test); - context.complete(); - } catch (IOException | SQLException e) { - log.fatal(e); - } finally { - if (context != null && context.isValid()) { - // Nothing is actually written - context.abort(); - } - } - } - - private static List filterOutToday(List completeList) { - log.debug("Filtering out all today item to leave new items list size=" - + completeList.size()); - List filteredList = new ArrayList<>(); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - String today = sdf.format(new Date()); - // Get the start and end dates for yesterday - Date thisTimeYesterday = new Date(System.currentTimeMillis() - - (24 * 60 * 60 * 1000)); - String yesterday = sdf.format(thisTimeYesterday); - - for (HarvestedItemInfo infoObject : completeList) { - Date lastUpdate = infoObject.item.getLastModified(); - String lastUpdateStr = sdf.format(lastUpdate); - - // has the item modified today? - if (lastUpdateStr.equals(today)) { - List dateAccArr = itemService.getMetadata(infoObject.item, "dc", - "date", "accessioned", Item.ANY); - // we need only the item archived yesterday - if (dateAccArr != null && dateAccArr.size() > 0) { - for (MetadataValue date : dateAccArr) { - if (date != null && date.getValue() != null) { - // if it hasn't been archived today - if (date.getValue().startsWith(yesterday)) { - filteredList.add(infoObject); - log.debug("adding : " + dateAccArr.get(0).getValue() - + " : " + today + " : " - + infoObject.handle); - break; - } else { - log.debug("ignoring : " + dateAccArr.get(0).getValue() - + " : " + today + " : " - + infoObject.handle); - } - } - } - } else { - log.debug("no date accessioned, adding : " - + infoObject.handle); - filteredList.add(infoObject); - } - } else { - // the item has been modified yesterday... - filteredList.add(infoObject); - } - } - - return filteredList; - } - - private static List filterOutModified(List completeList) { - log.debug("Filtering out all modified to leave new items list size=" + completeList.size()); - List filteredList = new ArrayList<>(); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - // Get the start and end dates for yesterday - Date thisTimeYesterday = new Date(System.currentTimeMillis() - - (24 * 60 * 60 * 1000)); - String yesterday = sdf.format(thisTimeYesterday); - - for (HarvestedItemInfo infoObject : completeList) { - List dateAccArr = itemService - .getMetadata(infoObject.item, "dc", "date", "accessioned", Item.ANY); - - if (dateAccArr != null && dateAccArr.size() > 0) { - for (MetadataValue date : dateAccArr) { - if (date != null && date.getValue() != null) { - // if it has been archived yesterday - if (date.getValue().startsWith(yesterday)) { - filteredList.add(infoObject); - log.debug("adding : " + dateAccArr.get(0) - .getValue() + " : " + yesterday + " : " + infoObject - .handle); - break; - } else { - log.debug("ignoring : " + dateAccArr.get(0) - .getValue() + " : " + yesterday + " : " + infoObject - .handle); - } - } - } - - - } else { - log.debug("no date accessioned, adding : " + infoObject.handle); - filteredList.add(infoObject); - } - } - - return filteredList; - } -} diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index be8564a0e635..13d8e0bef849 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -289,13 +289,6 @@ org.dspace.administer.StructBuilder - - sub-daily - Send daily subscription notices - - org.dspace.eperson.SubscribeCLITool - - test-email Test the DSpace email server settings are OK From 6069e32353a0b90d053e8c15876bcc43b5c71133 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 2 Feb 2023 16:04:35 +0100 Subject: [PATCH 214/394] [CST-7756] updated license header --- .../src/test/java/org/dspace/eperson/SubscribeServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java index 3105d183199a..b376dfe99986 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java @@ -1,4 +1,4 @@ -/* +/** * 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 From 8077139c672a7e5bbb8f95007626e8465865dd71 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 2 Feb 2023 16:23:20 +0100 Subject: [PATCH 215/394] 97425: Fix minor issues --- .../org/dspace/alerts/AllowSessionsEnum.java | 23 +++++----- .../org/dspace/alerts/SystemWideAlert.java | 6 +-- .../alerts/SystemWideAlertServiceImpl.java | 4 +- .../V7.5_2022.12.15__system_wide_alerts.sql | 2 +- .../V7.5_2022.12.15__system_wide_alerts.sql | 2 +- .../V7.5_2022.12.15__system_wide_alerts.sql | 2 +- .../alerts/SystemWideAlertServiceTest.java | 8 ++-- .../builder/SystemWideAlertBuilder.java | 18 -------- .../converter/SystemWideAlertConverter.java | 2 +- .../app/rest/model/SystemWideAlertRest.java | 6 +-- .../SystemWideAlertRestRepository.java | 6 +-- .../rest/SystemWideAlertRestRepositoryIT.java | 42 +++++++++---------- 12 files changed, 52 insertions(+), 69 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java index b43b350f7c2f..a200cab8781f 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java +++ b/dspace-api/src/main/java/org/dspace/alerts/AllowSessionsEnum.java @@ -18,34 +18,35 @@ * sessions. */ public enum AllowSessionsEnum { - ALLOW_ALL_SESSIONS(0), - ALLOW_CURRENT_SESSIONS_ONLY(1), - ALLOW_ADMIN_SESSIONS_ONLY(2); + ALLOW_ALL_SESSIONS("all"), + ALLOW_CURRENT_SESSIONS_ONLY("current"), + ALLOW_ADMIN_SESSIONS_ONLY("admin"); - private int allowSessionsType; + private String allowSessionsType; - AllowSessionsEnum(int allowSessionsType) { + AllowSessionsEnum(String allowSessionsType) { this.allowSessionsType = allowSessionsType; } - public int getValue() { + public String getValue() { return allowSessionsType; } - public static AllowSessionsEnum fromInt(Integer alertAllowSessionType) { + public static AllowSessionsEnum fromString(String alertAllowSessionType) { if (alertAllowSessionType == null) { return AllowSessionsEnum.ALLOW_ALL_SESSIONS; } switch (alertAllowSessionType) { - case 0: + case "all": return AllowSessionsEnum.ALLOW_ALL_SESSIONS; - case 1: + case "current": return AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY; - case 2: + case "admin" : return AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY; default: - throw new IllegalArgumentException("No corresponding enum value for integer"); + throw new IllegalArgumentException("No corresponding enum value for provided string: " + + alertAllowSessionType); } } diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java index debf683d6492..f56cbdcce9e9 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java @@ -43,7 +43,7 @@ public class SystemWideAlert implements ReloadableEntity { private String message; @Column(name = "allow_sessions") - private int allowSessions; + private String allowSessions; @Column(name = "countdown_to") @Temporal(TemporalType.TIMESTAMP) @@ -97,8 +97,8 @@ public void setMessage(final String message) { * * @return what kind of sessions are allowed while the system-wide alert is active */ - public int getAllowSessions() { - return allowSessions; + public AllowSessionsEnum getAllowSessions() { + return AllowSessionsEnum.fromString(allowSessions); } /** diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java index d0e98139d2fe..9ddf6c97d111 100644 --- a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlertServiceImpl.java @@ -112,7 +112,7 @@ public boolean canNonAdminUserLogin(Context context) throws SQLException { if (active == null || active.isEmpty()) { return true; } - return active.get(0).getAllowSessions() == AllowSessionsEnum.ALLOW_ALL_SESSIONS.getValue(); + return active.get(0).getAllowSessions() == AllowSessionsEnum.ALLOW_ALL_SESSIONS; } @Override @@ -124,6 +124,6 @@ public boolean canUserMaintainSession(Context context, EPerson ePerson) throws S if (active == null || active.isEmpty()) { return true; } - return active.get(0).getAllowSessions() != AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY.getValue(); + return active.get(0).getAllowSessions() != AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY; } } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.15__system_wide_alerts.sql index 148ad5cc3ca0..9d13138fdada 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.15__system_wide_alerts.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.15__system_wide_alerts.sql @@ -16,7 +16,7 @@ CREATE TABLE systemwidealert ( alert_id INTEGER NOT NULL PRIMARY KEY, message VARCHAR(512), - allow_sessions INTEGER, + allow_sessions VARCHAR(64), countdown_to TIMESTAMP, active BOOLEAN ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql index 148ad5cc3ca0..9d13138fdada 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.15__system_wide_alerts.sql @@ -16,7 +16,7 @@ CREATE TABLE systemwidealert ( alert_id INTEGER NOT NULL PRIMARY KEY, message VARCHAR(512), - allow_sessions INTEGER, + allow_sessions VARCHAR(64), countdown_to TIMESTAMP, active BOOLEAN ); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.15__system_wide_alerts.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.15__system_wide_alerts.sql index 148ad5cc3ca0..9d13138fdada 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.15__system_wide_alerts.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.15__system_wide_alerts.sql @@ -16,7 +16,7 @@ CREATE TABLE systemwidealert ( alert_id INTEGER NOT NULL PRIMARY KEY, message VARCHAR(512), - allow_sessions INTEGER, + allow_sessions VARCHAR(64), countdown_to TIMESTAMP, active BOOLEAN ); diff --git a/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java b/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java index 7355037ff1d3..5d8d6ac594a6 100644 --- a/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/alerts/SystemWideAlertServiceTest.java @@ -133,7 +133,7 @@ public void testDelete() throws Exception { @Test public void canNonAdminUserLoginTrueTest() throws Exception { // Mock the alert state - when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ALL_SESSIONS.getValue()); + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ALL_SESSIONS); // Mock DAO to return our defined systemWideAlertList List systemWideAlertList = new ArrayList<>(); @@ -147,7 +147,7 @@ public void canNonAdminUserLoginTrueTest() throws Exception { @Test public void canNonAdminUserLoginFalseTest() throws Exception { // Mock the alert state - when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY.getValue()); + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY); // Mock DAO to return our defined systemWideAlertList List systemWideAlertList = new ArrayList<>(); @@ -169,7 +169,7 @@ public void canUserMaintainSessionTrueTest() throws Exception { when(authorizeService.isAdmin(context, eperson)).thenReturn(false); // Mock the alert state - when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY); // Mock DAO to return our defined systemWideAlertList List systemWideAlertList = new ArrayList<>(); @@ -186,7 +186,7 @@ public void canUserMaintainSessionFalseTest() throws Exception { when(authorizeService.isAdmin(context, eperson)).thenReturn(false); // Mock the alert state - when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY.getValue()); + when(systemWideAlert.getAllowSessions()).thenReturn(AllowSessionsEnum.ALLOW_ADMIN_SESSIONS_ONLY); // Mock DAO to return our defined systemWideAlertList List systemWideAlertList = new ArrayList<>(); diff --git a/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java b/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java index dbc902b97a0c..cb6489815235 100644 --- a/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/SystemWideAlertBuilder.java @@ -7,7 +7,6 @@ */ package org.dspace.builder; -import java.io.IOException; import java.sql.SQLException; import java.util.Date; @@ -16,7 +15,6 @@ import org.dspace.alerts.service.SystemWideAlertService; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; -import org.dspace.scripts.Process; public class SystemWideAlertBuilder extends AbstractBuilder { @@ -93,20 +91,4 @@ public void delete(Context c, SystemWideAlert alert) throws Exception { getService().delete(c, alert); } } - - public static void deleteProcess(Integer integer) throws SQLException, IOException { - try (Context c = new Context()) { - c.turnOffAuthorisationSystem(); - Process process = processService.find(c, integer); - if (process != null) { - try { - processService.delete(c, process); - } catch (AuthorizeException e) { - // cannot occur, just wrap it to make the compiler happy - throw new RuntimeException(e); - } - } - c.complete(); - } - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java index 6f02e65008c0..419f2cf1d16b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SystemWideAlertConverter.java @@ -26,7 +26,7 @@ public SystemWideAlertRest convert(SystemWideAlert systemWideAlert, Projection p systemWideAlertRest.setId(systemWideAlert.getID()); systemWideAlertRest.setAlertId(systemWideAlert.getID()); systemWideAlertRest.setMessage(systemWideAlert.getMessage()); - systemWideAlertRest.setAllowSessions(systemWideAlert.getAllowSessions()); + systemWideAlertRest.setAllowSessions(systemWideAlert.getAllowSessions().getValue()); systemWideAlertRest.setCountdownTo(systemWideAlert.getCountdownTo()); systemWideAlertRest.setActive(systemWideAlert.isActive()); return systemWideAlertRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java index e82dc0b7c8b5..995ec8e93404 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SystemWideAlertRest.java @@ -36,7 +36,7 @@ public String getType() { private Integer alertId; private String message; - private Integer allowSessions; + private String allowSessions; private Date countdownTo; private boolean active; @@ -56,11 +56,11 @@ public void setMessage(final String message) { this.message = message; } - public Integer getAllowSessions() { + public String getAllowSessions() { return allowSessions; } - public void setAllowSessions(final Integer allowSessions) { + public void setAllowSessions(final String allowSessions) { this.allowSessions = allowSessions; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java index 6b2e28ebee40..73544145b20f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SystemWideAlertRestRepository.java @@ -80,7 +80,7 @@ public SystemWideAlertRest findOne(Context context, Integer id) { throw new ResourceNotFoundException( "systemWideAlert with id " + systemWideAlert.getID() + " was not found"); } - if (!authorizeService.isAdmin(context) && !systemWideAlert.isActive()) { + if (!systemWideAlert.isActive() && !authorizeService.isAdmin(context)) { throw new AuthorizeException("Non admin users are not allowed to retrieve inactive alerts"); } return converter.toRest(systemWideAlert, utils.obtainProjection()); @@ -125,7 +125,7 @@ protected SystemWideAlertRest put(Context context, HttpServletRequest request, S } systemWideAlert.setMessage(systemWideAlertRest.getMessage()); - systemWideAlert.setAllowSessions(AllowSessionsEnum.fromInt(systemWideAlertRest.getAllowSessions())); + systemWideAlert.setAllowSessions(AllowSessionsEnum.fromString(systemWideAlertRest.getAllowSessions())); systemWideAlert.setCountdownTo(systemWideAlertRest.getCountdownTo()); systemWideAlert.setActive(systemWideAlertRest.isActive()); @@ -166,7 +166,7 @@ private SystemWideAlert createSystemWideAlert(Context context) try { systemWideAlert = systemWideAlertService.create(context, systemWideAlertRest.getMessage(), - AllowSessionsEnum.fromInt( + AllowSessionsEnum.fromString( systemWideAlertRest.getAllowSessions()), systemWideAlertRest.getCountdownTo(), systemWideAlertRest.isActive()); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java index 0a79452f2190..beb979dfe68f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java @@ -68,7 +68,7 @@ public void findAllTest() throws Exception { allOf( hasJsonPath("$.alertId", is(systemWideAlert1.getID())), hasJsonPath("$.message", is(systemWideAlert1.getMessage())), - hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions())), + hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions().getValue())), hasJsonPath("$.countdownTo", startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), hasJsonPath("$.active", is(systemWideAlert1.isActive())) @@ -76,7 +76,7 @@ public void findAllTest() throws Exception { allOf( hasJsonPath("$.alertId", is(systemWideAlert2.getID())), hasJsonPath("$.message", is(systemWideAlert2.getMessage())), - hasJsonPath("$.allowSessions", is(systemWideAlert2.getAllowSessions())), + hasJsonPath("$.allowSessions", is(systemWideAlert2.getAllowSessions().getValue())), hasJsonPath("$.countdownTo", is(systemWideAlert2.getCountdownTo())), hasJsonPath("$.active", is(systemWideAlert2.isActive())) ) @@ -168,7 +168,7 @@ public void findOneTest() throws Exception { hasJsonPath("$.alertId", is(systemWideAlert1.getID())), hasJsonPath("$.message", is(systemWideAlert1.getMessage())), hasJsonPath("$.allowSessions", - is(systemWideAlert1.getAllowSessions())), + is(systemWideAlert1.getAllowSessions().getValue())), hasJsonPath("$.countdownTo", startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), hasJsonPath("$.active", is(systemWideAlert1.isActive())) @@ -209,7 +209,7 @@ public void findOneUnauthorizedTest() throws Exception { hasJsonPath("$.alertId", is(systemWideAlert1.getID())), hasJsonPath("$.message", is(systemWideAlert1.getMessage())), hasJsonPath("$.allowSessions", - is(systemWideAlert1.getAllowSessions())), + is(systemWideAlert1.getAllowSessions().getValue())), hasJsonPath("$.countdownTo", startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), hasJsonPath("$.active", is(systemWideAlert1.isActive())) @@ -255,7 +255,7 @@ public void findOneForbiddenTest() throws Exception { hasJsonPath("$.alertId", is(systemWideAlert1.getID())), hasJsonPath("$.message", is(systemWideAlert1.getMessage())), hasJsonPath("$.allowSessions", - is(systemWideAlert1.getAllowSessions())), + is(systemWideAlert1.getAllowSessions().getValue())), hasJsonPath("$.countdownTo", startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), hasJsonPath("$.active", is(systemWideAlert1.isActive())) @@ -303,19 +303,19 @@ public void findAllActiveTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.systemwidealerts", containsInAnyOrder( allOf( - hasJsonPath("$.alertId", is(systemWideAlert1.getID())), - hasJsonPath("$.message", is(systemWideAlert1.getMessage())), - hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions())), - hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), - hasJsonPath("$.active", is(systemWideAlert1.isActive())) + hasJsonPath("$.alertId", is(systemWideAlert1.getID())), + hasJsonPath("$.message", is(systemWideAlert1.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions().getValue())), + hasJsonPath("$.countdownTo", + startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + hasJsonPath("$.active", is(systemWideAlert1.isActive())) ), allOf( - hasJsonPath("$.alertId", is(systemWideAlert3.getID())), - hasJsonPath("$.message", is(systemWideAlert3.getMessage())), - hasJsonPath("$.allowSessions", is(systemWideAlert3.getAllowSessions())), - hasJsonPath("$.countdownTo", is(systemWideAlert3.getCountdownTo())), - hasJsonPath("$.active", is(systemWideAlert3.isActive())) + hasJsonPath("$.alertId", is(systemWideAlert3.getID())), + hasJsonPath("$.message", is(systemWideAlert3.getMessage())), + hasJsonPath("$.allowSessions", is(systemWideAlert3.getAllowSessions().getValue())), + hasJsonPath("$.countdownTo", is(systemWideAlert3.getCountdownTo())), + hasJsonPath("$.active", is(systemWideAlert3.isActive())) ) ))); @@ -326,7 +326,7 @@ public void createTest() throws Exception { SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); systemWideAlertRest.setMessage("Alert test message"); systemWideAlertRest.setCountdownTo(new Date()); - systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); systemWideAlertRest.setActive(true); ObjectMapper mapper = new ObjectMapper(); @@ -376,7 +376,7 @@ public void createForbiddenTest() throws Exception { SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); systemWideAlertRest.setMessage("Alert test message"); systemWideAlertRest.setCountdownTo(new Date()); - systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); systemWideAlertRest.setActive(true); ObjectMapper mapper = new ObjectMapper(); @@ -396,7 +396,7 @@ public void createUnAuthorizedTest() throws Exception { SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); systemWideAlertRest.setMessage("Alert test message"); systemWideAlertRest.setCountdownTo(new Date()); - systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); systemWideAlertRest.setActive(true); ObjectMapper mapper = new ObjectMapper(); @@ -425,7 +425,7 @@ public void createWhenAlreadyExistsTest() throws Exception { SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); systemWideAlertRest.setMessage("Alert test message"); systemWideAlertRest.setCountdownTo(new Date()); - systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); systemWideAlertRest.setActive(true); ObjectMapper mapper = new ObjectMapper(); @@ -454,7 +454,7 @@ public void putTest() throws Exception { systemWideAlertRest.setAlertId(systemWideAlert.getID()); systemWideAlertRest.setMessage("Updated alert test message"); systemWideAlertRest.setCountdownTo(new Date()); - systemWideAlertRest.setAllowSessions(1); + systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); systemWideAlertRest.setActive(true); ObjectMapper mapper = new ObjectMapper(); From b4f7ebfb8a487e26f2f36f60272d44bad71bf8c3 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 2 Feb 2023 17:47:08 +0100 Subject: [PATCH 216/394] [CST-7756] added instructions to migrate subscriptions from previous versions --- ...ramter_change_columns_subscription_table.sql | 15 ++++++++++----- ...ramter_change_columns_subscription_table.sql | 17 ++++++++++------- ...ramter_change_columns_subscription_table.sql | 14 ++++++++------ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql index e94ec3c5049f..7eb1697c9313 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -25,17 +25,22 @@ CREATE TABLE subscription_parameter CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE ); --- -- -ALTER TABLE subscription DROP CONSTRAINT Subscription_collection_id_fk; --- -ALTER TABLE subscription DROP COLUMN collection_id; + -- ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; -- ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); -- ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); - +-- -- +UPDATE subscription set dspace_object_id = collection_id , type = 'content'; +-- +ALTER TABLE subscription DROP CONSTRAINT Subscription_collection_id_fk; +-- +ALTER TABLE subscription DROP COLUMN collection_id; +-- +INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id) +SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql index 8db385f5acf4..fe81c1743abe 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -26,16 +26,19 @@ CREATE TABLE subscription_parameter REFERENCES subscription (subscription_id) ON DELETE CASCADE ); -- -- -ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; ----- -- -ALTER TABLE subscription DROP COLUMN collection_id; --- + ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; ---- -- ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); - +-- +UPDATE subscription SET dspace_object_id = collection_id , type = 'content'; +-- ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- - - +ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; +---- -- +ALTER TABLE subscription DROP COLUMN collection_id; +-- -- +INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id) +SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql index 5313472706ee..f09e26b9f8e5 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -24,10 +24,6 @@ CREATE TABLE subscription_parameter CONSTRAINT subscription_parameter_pkey PRIMARY KEY (subscription_parameter_id), CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE ); - -- -ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; --- -- -ALTER TABLE subscription DROP COLUMN collection_id; -- ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; -- -- @@ -35,6 +31,12 @@ ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); ---- -- ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- - - +UPDATE subscription SET dspace_object_id = collection_id , type = 'content'; +-- +ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; +-- -- +ALTER TABLE subscription DROP COLUMN collection_id; +-- -- +INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id) +SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ; From 0edaba6f3a5fe16edfeb257106f946e92ebf0f9d Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 2 Feb 2023 17:54:09 +0100 Subject: [PATCH 217/394] [CST-7756] after merge checkstyle fix --- .../src/main/java/org/dspace/content/ItemServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index e289d399be53..e73c49ee07b4 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -58,8 +58,8 @@ import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.dspace.eperson.service.SubscribeService; import org.dspace.eperson.service.GroupService; +import org.dspace.eperson.service.SubscribeService; import org.dspace.event.Event; import org.dspace.harvest.HarvestedItem; import org.dspace.harvest.service.HarvestedItemService; From d3c79bd2cb7a1232c9408a78da75c23ee499ad3a Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 2 Feb 2023 18:01:24 +0100 Subject: [PATCH 218/394] [CST-7756] extended abstract test --- .../test/java/org/dspace/eperson/SubscribeServiceTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java index b376dfe99986..c743969f012e 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java @@ -27,21 +27,19 @@ import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.dspace.AbstractDSpaceTest; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.eperson.dao.SubscriptionDAO; import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; -@RunWith(MockitoJUnitRunner.class) -public class SubscribeServiceTest { +public class SubscribeServiceTest extends AbstractDSpaceTest { @Mock private SubscriptionDAO subscriptionDAO; From edcb771ef235abc29f3066f8a95a4ce64772120b Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 2 Feb 2023 18:04:07 +0100 Subject: [PATCH 219/394] [CST-7756] updated script --- ..._subscriptionparamter_change_columns_subscription_table.sql | 3 --- 1 file changed, 3 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql index 7eb1697c9313..1ec9d586bfdd 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -38,9 +38,6 @@ UPDATE subscription set dspace_object_id = collection_id , type = 'content'; ALTER TABLE subscription DROP CONSTRAINT Subscription_collection_id_fk; -- ALTER TABLE subscription DROP COLUMN collection_id; --- -INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id) -SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ; From 54f6ba9951686b3cbbea71542a420c778aaf966d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 2 Feb 2023 17:07:49 +0100 Subject: [PATCH 220/394] 99053: Add return_to_pool option to all ProcessingAction except SingleUserReviewAction --- .../actions/processingaction/AcceptEditRejectAction.java | 1 + .../state/actions/processingaction/FinalEditAction.java | 1 + .../state/actions/processingaction/ProcessingAction.java | 2 +- .../state/actions/processingaction/ReviewAction.java | 1 + .../state/actions/processingaction/ScoreEvaluationAction.java | 4 +++- .../state/actions/processingaction/ScoreReviewAction.java | 2 +- .../state/actions/processingaction/SelectReviewerAction.java | 1 + .../xmlworkflow/state/actions/userassignment/ClaimAction.java | 4 ++++ 8 files changed, 13 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java index be76502fca65..7adbd9b6b21e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java @@ -67,6 +67,7 @@ public List getOptions() { options.add(SUBMIT_APPROVE); options.add(SUBMIT_REJECT); options.add(ProcessingAction.SUBMIT_EDIT_METADATA); + options.add(RETURN_TO_POOL); return options; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java index 763ba37e73d7..9b83be5d7bfa 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java @@ -64,6 +64,7 @@ public List getOptions() { List options = new ArrayList<>(); options.add(SUBMIT_APPROVE); options.add(ProcessingAction.SUBMIT_EDIT_METADATA); + options.add(RETURN_TO_POOL); return options; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java index af0a17a44586..04509cf7a82d 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java @@ -41,7 +41,7 @@ public abstract class ProcessingAction extends Action { public static final String SUBMIT_CANCEL = "submit_cancel"; protected static final String SUBMIT_APPROVE = "submit_approve"; protected static final String SUBMIT_REJECT = "submit_reject"; - + protected static final String RETURN_TO_POOL = "return_to_pool"; protected static final String REJECT_REASON = "reason"; @Override diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java index 50cb2746a5ff..0e44f4b428bc 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java @@ -66,6 +66,7 @@ public List getOptions() { List options = new ArrayList<>(); options.add(SUBMIT_APPROVE); options.add(SUBMIT_REJECT); + options.add(RETURN_TO_POOL); return options; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java index ceb8eb5e7609..16d35b36683a 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java @@ -105,7 +105,9 @@ private void addRatingInfoToProv(Context c, XmlWorkflowItem wfi, int scoreMean) @Override public List getOptions() { - return new ArrayList<>(); + List options = new ArrayList<>(); + options.add(RETURN_TO_POOL); + return options; } public int getMinimumAcceptanceScore() { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java index e7fd434eeac7..35916094693a 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java @@ -118,7 +118,7 @@ private boolean checkRequestValid(int score, String review) { @Override public List getOptions() { - return Arrays.asList(SUBMIT_SCORE); + return List.of(SUBMIT_SCORE, RETURN_TO_POOL); } @Override diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java index b80009421c55..f0fe3cbb98ca 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java @@ -145,6 +145,7 @@ public ActionResult processSelectReviewers(Context c, XmlWorkflowItem wfi, Step public List getOptions() { List options = new ArrayList<>(); options.add(SUBMIT_SELECT_REVIEWER); + options.add(RETURN_TO_POOL); return options; } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java index c9c61908aab6..21fcf6f30996 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/ClaimAction.java @@ -138,6 +138,10 @@ public boolean isValidUserSelection(Context context, XmlWorkflowItem wfi, boolea RoleMembers roleMembers = role.getMembers(context, wfi); ArrayList epersons = roleMembers.getAllUniqueMembers(context); + if (epersons.isEmpty() || step.getRequiredUsers() > epersons.size()) { + log.warn(String.format("There must be at least %s ePerson(s) in the group", + step.getRequiredUsers())); + } return !(epersons.isEmpty() || step.getRequiredUsers() > epersons.size()); } else { // We don't have a role and do have a UI so throw a workflow exception From 98ee5cb44fd44ac5f77bc2644f396b47c6212bff Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 2 Feb 2023 19:08:28 +0100 Subject: [PATCH 221/394] Reinstate config accidentally deleted, added in 30f923a080aa0d927036e0eae609c3bf1a273d35 (#8064) - enables RSS/Atom feeds --- dspace/config/modules/rest.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 5618f6198547..5bb58b0eee30 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -32,6 +32,7 @@ rest.projection.specificLevel.maxEmbed = 5 # (Requires reboot of servlet container, e.g. Tomcat, to reload) rest.properties.exposed = plugin.named.org.dspace.curate.CurationTask rest.properties.exposed = google.analytics.key +rest.properties.exposed = websvc.opensearch.enable rest.properties.exposed = versioning.item.history.include.submitter rest.properties.exposed = researcher-profile.entity-type rest.properties.exposed = orcid.application-client-id From 00b53923a682391333415547f70874e7ad02634e Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 2 Feb 2023 19:23:54 +0100 Subject: [PATCH 222/394] [CST-7756] removed subscriptions for items --- .../org/dspace/eperson/FrequencyType.java | 38 ++++ .../dspace/eperson/SubscribeServiceImpl.java | 8 +- .../eperson/service/SubscribeService.java | 8 +- .../subscriptions/ContentGenerator.java | 4 +- .../SubscriptionEmailNotification.java | 10 +- .../SubscriptionEmailNotificationService.java | 4 - ...scriptionEmailNotificationServiceImpl.java | 33 +-- .../dSpaceObjectsUpdates/ItemUpdates.java | 193 ------------------ .../service/SubscriptionGenerator.java | 2 +- .../SubscriptionRestRepository.java | 76 +++---- ...dminAndOwnerPermissionEvaluatorPlugin.java | 73 ------- .../security/SubscriptionSecurityBean.java | 75 +++++++ .../rest/SubscriptionRestRepositoryIT.java | 124 +++++------ .../api/subscriptions_email_configuration.xml | 15 -- 14 files changed, 206 insertions(+), 457 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java delete mode 100644 dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemUpdates.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java diff --git a/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java new file mode 100644 index 000000000000..4dc09b491175 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.eperson; + +import java.util.Arrays; + +import org.apache.commons.codec.binary.StringUtils; + +public enum FrequencyType { + DAY("D"), + WEEK("W"), + MONTH("M"); + + private String shortName; + + private FrequencyType(String shortName) { + this.shortName = shortName; + } + + public static boolean isSupportedFrequencyType(String value) { + for (FrequencyType ft : Arrays.asList(FrequencyType.values())) { + if (StringUtils.equals(ft.getShortName(), value)) { + return true; + } + } + return false; + } + + public String getShortName() { + return shortName; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 3aae28af4982..7f5b9740ef3e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -152,15 +152,13 @@ public Subscription findById(Context context, int id) throws SQLException { } @Override - public Subscription updateSubscription(Context context, Integer id, EPerson eperson, DSpaceObject dSpaceObject, - List subscriptionParameterList, String type) + public Subscription updateSubscription(Context context, Integer id, String subscriptionType, + List subscriptionParameterList) throws SQLException { Subscription subscriptionDB = subscriptionDAO.findByID(context, Subscription.class, id); subscriptionDB.removeParameterList(); - subscriptionDB.setSubscriptionType(type); - subscriptionDB.setDSpaceObject(dSpaceObject); + subscriptionDB.setSubscriptionType(subscriptionType); subscriptionParameterList.forEach(x -> subscriptionDB.addParameter(x)); - subscriptionDB.setEPerson(eperson); subscriptionDAO.save(context, subscriptionDB); return subscriptionDB; } diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index c634088d7172..fa35e9992222 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -159,14 +159,12 @@ public List findSubscriptionsByEPersonAndDso(Context context, EPer * * @param context DSpace context * @param id Integer id - * @param eperson EPerson eperson - * @param dSpaceObject DSpaceObject dSpaceObject * @param subscriptionParameterList List subscriptionParameterList - * @param type String type + * @param subscriptionType type * @throws SQLException An exception that provides information on a database access error or other errors. */ - public Subscription updateSubscription(Context context, Integer id, EPerson eperson, DSpaceObject dSpaceObject, - List subscriptionParameterList, String type) throws SQLException; + public Subscription updateSubscription(Context context, Integer id, String subscriptionType, + List subscriptionParameterList) throws SQLException; /** * Adds a parameter to a subscription diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index e017cbe2f07c..c35729ad57bc 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -51,8 +51,7 @@ public class ContentGenerator implements SubscriptionGenerator @Override public void notifyForSubscriptions(Context context, EPerson ePerson, List indexableComm, - List indexableColl, - List indexableItems) { + List indexableColl) { try { if (Objects.nonNull(ePerson)) { Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson); @@ -60,7 +59,6 @@ public void notifyForSubscriptions(Context context, EPerson ePerson, email.addRecipient(ePerson.getEmail()); email.addArgument(generateBodyMail(context, indexableComm)); email.addArgument(generateBodyMail(context, indexableColl)); - email.addArgument(generateBodyMail(context, indexableItems)); email.send(); } } catch (Exception e) { diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java index 7cfa9bd33451..9be4816fe87b 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java @@ -9,13 +9,13 @@ import java.sql.SQLException; import java.util.Objects; -import java.util.Set; import java.util.UUID; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.eperson.FrequencyType; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.scripts.DSpaceRunnable; import org.dspace.utils.DSpace; @@ -52,10 +52,10 @@ public void internalRun() throws Exception { if (StringUtils.isBlank(frequencyOption)) { throw new IllegalArgumentException("Option frequency f must be set"); } - Set supportedValues = subscriptionEmailNotificationService - .getSubscriptionParameterValuesByName("frequency"); - if (!supportedValues.contains(frequencyOption)) { - throw new IllegalArgumentException("Option f must be one of following values:" + supportedValues); + + if (!FrequencyType.isSupportedFrequencyType(frequencyOption)) { + throw new IllegalArgumentException( + "Option f must be one of following values D(Day), W(Week) or M(Month)"); } subscriptionEmailNotificationService.perform(getContext(), handler, "content", frequencyOption); } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java index d0b29b92fec4..5cf7c09a0a76 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java @@ -21,8 +21,4 @@ public interface SubscriptionEmailNotificationService { public Set getSupportedSubscriptionTypes(); - public boolean isSupportedSubscriptionParameterName(String name); - - public Set getSubscriptionParameterValuesByName(String name); - } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java index 4dd618cc69e8..1db4672422c4 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -9,11 +9,9 @@ import static org.dspace.core.Constants.COLLECTION; import static org.dspace.core.Constants.COMMUNITY; -import static org.dspace.core.Constants.ITEM; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -26,7 +24,6 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; import org.dspace.eperson.Subscription; @@ -46,13 +43,6 @@ public class SubscriptionEmailNotificationServiceImpl implements SubscriptionEma private static final Logger log = LogManager.getLogger(SubscriptionEmailNotificationServiceImpl.class); - /** - * The map contains supported {SubscriptionParameter} - * into the key configured {SubscriptionParameter.name} - * instead into value configured a set of values {SubscriptionParameter.value} - * related to {SubscriptionParameter.name} - */ - private Map> param2values = new HashMap<>(); private Map contentUpdates = new HashMap<>(); @SuppressWarnings("rawtypes") private Map subscriptionType2generators = new HashMap<>(); @@ -61,17 +51,14 @@ public class SubscriptionEmailNotificationServiceImpl implements SubscriptionEma private SubscribeService subscribeService; @SuppressWarnings("rawtypes") - public SubscriptionEmailNotificationServiceImpl(Map> param2values, - Map contentUpdates, + public SubscriptionEmailNotificationServiceImpl(Map contentUpdates, Map subscriptionType2generators) { - this.param2values = param2values; this.contentUpdates = contentUpdates; this.subscriptionType2generators = subscriptionType2generators; } @SuppressWarnings({ "rawtypes", "unchecked" }) public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency) { - List items = new ArrayList<>(); List communities = new ArrayList<>(); List collections = new ArrayList<>(); try { @@ -90,9 +77,6 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc } else if (dSpaceObject.getType() == COLLECTION) { collections.addAll(contentUpdates.get(Collection.class.getSimpleName()) .findUpdates(context, dSpaceObject, frequency)); - } else if (dSpaceObject.getType() == ITEM) { - items.addAll(contentUpdates.get(Item.class.getSimpleName()) - .findUpdates(context, dSpaceObject, frequency)); } var ePerson = subscription.getEPerson(); @@ -103,15 +87,14 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc continue; } else { subscriptionType2generators.get(subscriptionType) - .notifyForSubscriptions(context, ePerson, communities, collections, items); + .notifyForSubscriptions(context, ePerson, communities, collections); communities.clear(); collections.clear(); - items.clear(); } } else { //in the end of the iteration subscriptionType2generators.get(subscriptionType) - .notifyForSubscriptions(context, ePerson, communities, collections, items); + .notifyForSubscriptions(context, ePerson, communities, collections); } iterator++; } @@ -154,14 +137,4 @@ public Set getSupportedSubscriptionTypes() { return subscriptionType2generators.keySet(); } - @Override - public Set getSubscriptionParameterValuesByName(String name) { - return param2values.containsKey(name) ? param2values.get(name) : Collections.emptySet(); - } - - @Override - public boolean isSupportedSubscriptionParameterName(String name) { - return param2values.containsKey(name); - } - } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemUpdates.java deleted file mode 100644 index 5c8a290a45f1..000000000000 --- a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemUpdates.java +++ /dev/null @@ -1,193 +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.subscriptions.dSpaceObjectsUpdates; - -import static org.apache.commons.lang3.StringUtils.trimToEmpty; - -import java.sql.SQLException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; -import org.dspace.content.service.CollectionService; -import org.dspace.content.service.CommunityService; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.discovery.DiscoverQuery; -import org.dspace.discovery.DiscoverResult; -import org.dspace.discovery.IndexableObject; -import org.dspace.discovery.SearchService; -import org.dspace.discovery.configuration.DiscoveryConfiguration; -import org.dspace.discovery.configuration.DiscoveryConfigurationService; -import org.dspace.discovery.configuration.DiscoveryRelatedItemConfiguration; -import org.dspace.discovery.configuration.DiscoverySortConfiguration; -import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration; -import org.dspace.discovery.configuration.DiscoverySortFunctionConfiguration; -import org.dspace.discovery.indexobject.IndexableCollection; -import org.dspace.discovery.indexobject.IndexableCommunity; -import org.dspace.discovery.indexobject.IndexableItem; -import org.dspace.subscriptions.service.DSpaceObjectUpdates; - -/** - * Class which will be used to find - * all item objects updated related with subscribed DSO - * - * @author Alba Aliu - */ -public class ItemUpdates implements DSpaceObjectUpdates { - - private final Logger log = LogManager.getLogger(ItemUpdates.class); - - private final CollectionService collectionService; - private final CommunityService communityService; - private final ItemService itemService; - private DiscoveryConfigurationService searchConfigurationService; - private SearchService searchService; - - @Override - @SuppressWarnings("rawtypes") - public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) { - List list = new ArrayList<>(); - // entity type found - String inverseRelationName = "RELATION." + itemService.getEntityTypeLabel((Item) dSpaceObject); - List discoveryConfigurationList = - searchConfigurationService.getDiscoveryConfigurationWithPrefixName(inverseRelationName); - DiscoverQuery discoverQuery = null; - DiscoverResult searchResult = null; - IndexableObject indexableObject = resolveScope(context, dSpaceObject.getID().toString()); - try { - for (DiscoveryConfiguration discoveryConfiguration : discoveryConfigurationList) { - discoverQuery = buildDiscoveryQuery(discoveryConfiguration, indexableObject); - discoverQuery.addFilterQueries("lastModified_dt:" + this.findLastFrequency(frequency)); - searchResult = searchService.search(context, discoverQuery); - list.addAll(searchResult.getIndexableObjects()); - } - } catch (Exception e) { - log.error(e.getMessage(), e); - } - return list; - } - - private IndexableObject resolveScope(Context context, String scope) { - IndexableObject scopeObj = null; - if (StringUtils.isBlank(scope)) { - return scopeObj; - } - try { - UUID uuid = UUID.fromString(scope); - scopeObj = new IndexableCommunity(communityService.find(context, uuid)); - if (scopeObj.getIndexedObject() == null) { - scopeObj = new IndexableCollection(collectionService.find(context, uuid)); - } - if (scopeObj.getIndexedObject() == null) { - scopeObj = new IndexableItem(itemService.find(context, uuid)); - } - } catch (IllegalArgumentException e) { - log.error("The given scope string " + trimToEmpty(scope) + " is not a UUID"); - } catch (SQLException e) { - log.error("Unable to retrieve DSpace Object with ID " + trimToEmpty(scope) + " from the database"); - } - return scopeObj; - } - - private DiscoverQuery buildDiscoveryQuery(DiscoveryConfiguration discoveryConfiguration, IndexableObject scope) - throws SQLException { - DiscoverQuery discoverQuery = buildBaseQuery(discoveryConfiguration, scope); - discoverQuery.addDSpaceObjectFilter(IndexableItem.TYPE); - configureSorting(discoverQuery, discoveryConfiguration.getSearchSortConfiguration(), scope); - return discoverQuery; - } - - @SuppressWarnings("rawtypes") - private void configureSorting(DiscoverQuery queryArgs, DiscoverySortConfiguration searchSortConfiguration, - final IndexableObject scope) { - String sortBy = getDefaultSortField(searchSortConfiguration); - String sortOrder = getDefaultSortDirection(searchSortConfiguration); - //Update Discovery query - DiscoverySortFieldConfiguration sortFieldConfiguration = searchSortConfiguration - .getSortFieldConfiguration(sortBy); - - if (Objects.nonNull(sortFieldConfiguration)) { - String sortField; - if (DiscoverySortFunctionConfiguration.SORT_FUNCTION.equals(sortFieldConfiguration.getType())) { - sortField = MessageFormat.format( - ((DiscoverySortFunctionConfiguration) sortFieldConfiguration).getFunction(scope.getID()), - scope.getID()); - } else { - var type = sortFieldConfiguration.getType(); - var metadataField = sortFieldConfiguration.getMetadataField(); - sortField = searchService.toSortFieldIndex(metadataField, type); - } - - if ("asc".equalsIgnoreCase(sortOrder)) { - queryArgs.setSortField(sortField, DiscoverQuery.SORT_ORDER.asc); - } else if ("desc".equalsIgnoreCase(sortOrder)) { - queryArgs.setSortField(sortField, DiscoverQuery.SORT_ORDER.desc); - } else { - log.error(sortOrder + " is not a valid sort order"); - } - - } else { - log.error(sortBy + " is not a valid sort field"); - } - } - - private String getDefaultSortDirection(DiscoverySortConfiguration searchSortConfiguration) { - return searchSortConfiguration.getSortFields().iterator().next().getDefaultSortOrder().toString(); - } - - private String getDefaultSortField(DiscoverySortConfiguration searchSortConfiguration) { - String sortBy;// Attempt to find the default one, if none found we use SCORE - sortBy = "score"; - if (Objects.nonNull(searchSortConfiguration.getSortFields()) && - !searchSortConfiguration.getSortFields().isEmpty()) { - DiscoverySortFieldConfiguration defaultSort = searchSortConfiguration.getSortFields().get(0); - if (org.apache.commons.lang.StringUtils.isBlank(defaultSort.getMetadataField())) { - return sortBy; - } - sortBy = defaultSort.getMetadataField(); - } - return sortBy; - } - - private DiscoverQuery buildBaseQuery(DiscoveryConfiguration discoveryConfiguration, IndexableObject scope) { - DiscoverQuery discoverQuery = new DiscoverQuery(); - if (Objects.isNull(discoveryConfiguration)) { - return discoverQuery; - } - - discoverQuery.setDiscoveryConfigurationName(discoveryConfiguration.getId()); - List filterQueries = discoveryConfiguration.getDefaultFilterQueries(); - - for (String filterQuery : filterQueries) { - if (discoveryConfiguration instanceof DiscoveryRelatedItemConfiguration) { - discoverQuery.addFilterQueries(MessageFormat.format(filterQuery, scope.getID())); - } else { - discoverQuery.addFilterQueries(filterQuery); - } - } - return discoverQuery; - } - - public ItemUpdates(CollectionService collectionService, CommunityService communityService, ItemService itemService, - DiscoveryConfigurationService searchConfigurationService, SearchService searchService) { - this.collectionService = collectionService; - this.communityService = communityService; - this.itemService = itemService; - this.searchConfigurationService = searchConfigurationService; - this.searchService = searchService; - } - -} diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java index 994ada75b61b..1790513b9b79 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java @@ -20,6 +20,6 @@ */ public interface SubscriptionGenerator { - public void notifyForSubscriptions(Context c, EPerson ePerson, List comm, List coll, List items); + public void notifyForSubscriptions(Context c, EPerson ePerson, List comm, List coll); } \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 2342b026247b..3cf2699d8802 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -9,6 +9,8 @@ import static org.dspace.app.rest.model.SubscriptionRest.CATEGORY; import static org.dspace.app.rest.model.SubscriptionRest.NAME; +import static org.dspace.core.Constants.COLLECTION; +import static org.dspace.core.Constants.COMMUNITY; import java.io.IOException; import java.sql.SQLException; @@ -24,6 +26,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; @@ -33,11 +36,10 @@ import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; -import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.eperson.FrequencyType; import org.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; import org.dspace.eperson.service.EPersonService; @@ -66,8 +68,6 @@ public class SubscriptionRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { } @SearchRestMethod(name = "findByEPerson") - @PreAuthorize("hasPermission(#epersonId, 'AdminOrOwner', 'READ')") + @PreAuthorize("hasPermission(#epersonId, 'EPERSON', 'READ')") public Page findSubscriptionsByEPerson(@Parameter(value = "uuid", required = true) UUID epersonId, Pageable pageable) throws Exception { Long total = null; @@ -122,7 +122,7 @@ public Page findSubscriptionsByEPerson(@Parameter(value = "uui } @SearchRestMethod(name = "findByEPersonAndDso") - @PreAuthorize("hasPermission(#epersonId, 'AdminOrOwner', 'READ')") + @PreAuthorize("hasPermission(#epersonId, 'EPERSON', 'READ')") public Page findByEPersonAndDso(@Parameter(value = "eperson_id", required = true) UUID epersonId, @Parameter(value = "resource",required = true) UUID dsoId, Pageable pageable) throws Exception { @@ -143,6 +143,7 @@ public Page findByEPersonAndDso(@Parameter(value = "eperson_id } @Override + @PreAuthorize("@subscriptionSecurity.isEnabelToCreateSubscription(#context) && hasAuthority('AUTHENTICATED')") protected SubscriptionRest createAndReturn(Context context) throws SQLException, AuthorizeException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); @@ -159,31 +160,23 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, throw new BadRequestException("Id of person or dspace object must represents reals ids"); } - // user must have read permissions to dataspace object - if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, Constants.READ, true)) { - throw new AuthorizeException("The user has not READ rights on this DSO"); - } - - // if user is admin do not make this control, - // otherwise make this control because normal user can only subscribe with their own ID of user. - if (!authorizeService.isAdmin(context)) { - if (!ePerson.equals(context.getCurrentUser())) { - throw new AuthorizeException("Only administrator can subscribe for other persons"); + if (dSpaceObject.getType() == COMMUNITY || dSpaceObject.getType() == COLLECTION) { + Subscription subscription = null; + ServletInputStream input = req.getInputStream(); + SubscriptionRest subscriptionRest = new ObjectMapper().readValue(input, SubscriptionRest.class); + List subscriptionParameterList = subscriptionRest + .getSubscriptionParameterList(); + if (CollectionUtils.isNotEmpty(subscriptionParameterList)) { + List subscriptionParameters = new ArrayList<>(); + validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); + subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, + subscriptionRest.getSubscriptionType()); } + context.commit(); + return converter.toRest(subscription, utils.obtainProjection()); + } else { + throw new BadRequestException("Currently subscription is supported only for Community and Collection"); } - - Subscription subscription = null; - ServletInputStream input = req.getInputStream(); - SubscriptionRest subscriptionRest = new ObjectMapper().readValue(input, SubscriptionRest.class); - List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); - if (CollectionUtils.isNotEmpty(subscriptionParameterList)) { - List subscriptionParameters = new ArrayList<>(); - validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); - subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, - subscriptionRest.getSubscriptionType()); - } - context.commit(); - return converter.toRest(subscription, utils.obtainProjection()); } catch (SQLException sqlException) { throw new SQLException(sqlException.getMessage(), sqlException); } catch (IOException ioException) { @@ -198,7 +191,7 @@ private void validateParameters(SubscriptionRest subscriptionRest, SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); var name = subscriptionParameterRest.getName(); var value = subscriptionParameterRest.getValue(); - if (!nameAndValueAreSupported(name, value)) { + if (!StringUtils.equals("frequency", name) || !FrequencyType.isSupportedFrequencyType(value)) { throw new UnprocessableEntityException("Provided SubscriptionParameter name:" + name + " or value: " + value + " is not supported!"); } @@ -214,20 +207,11 @@ private void validateParameters(SubscriptionRest subscriptionRest, } } - private boolean nameAndValueAreSupported(String name, String value) { - return subscriptionEmailNotificationService.isSupportedSubscriptionParameterName(name) && - subscriptionEmailNotificationService.getSubscriptionParameterValuesByName(name).contains(value); - } - @Override @PreAuthorize("hasPermission(#id, 'subscription', 'WRITE')") protected SubscriptionRest put(Context context, HttpServletRequest request, String apiCategory, String model, Integer id, JsonNode jsonNode) throws SQLException { - HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); - String epersonId = req.getParameter("eperson_id"); - String dsoId = req.getParameter("resource"); - SubscriptionRest subscriptionRest; try { subscriptionRest = new ObjectMapper().readValue(jsonNode.toString(), SubscriptionRest.class); @@ -240,22 +224,12 @@ protected SubscriptionRest put(Context context, HttpServletRequest request, Stri throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); } - EPerson ePerson = ePersonService.findByIdOrLegacyId(context, epersonId); - if (Objects.isNull(ePerson)) { - throw new ResourceNotFoundException("There is not ePerson with uuid:" + epersonId); - } - - DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); - if (Objects.isNull(dSpaceObject)) { - throw new ResourceNotFoundException("There is not DSpaceObject with uuid:" + dsoId); - } - if (id.equals(subscription.getID())) { List subscriptionParameters = new ArrayList<>(); List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); - subscription = subscribeService.updateSubscription(context, id, ePerson, dSpaceObject, - subscriptionParameters, subscriptionRest.getSubscriptionType()); + subscription = subscribeService.updateSubscription(context, id, subscriptionRest.getSubscriptionType(), + subscriptionParameters); context.commit(); return converter.toRest(subscription, utils.obtainProjection()); } else { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.java deleted file mode 100644 index 7e58afd60107..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.java +++ /dev/null @@ -1,73 +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.security; - -import java.io.Serializable; -import java.sql.SQLException; -import java.util.Objects; -import java.util.UUID; - -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; -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; - -/** - * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) - */ -@Component -public class SubscriptionAdminAndOwnerPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { - - private static final Logger log = LoggerFactory.getLogger(SubscriptionAdminAndOwnerPermissionEvaluatorPlugin.class); - - @Autowired - private RequestService requestService; - @Autowired - private AuthorizeService authorizeService; - - @Override - public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, - String targetType, DSpaceRestPermission permission) { - - DSpaceRestPermission restPermission = DSpaceRestPermission.convert(permission); - - if (!DSpaceRestPermission.READ.equals(restPermission) && - !DSpaceRestPermission.WRITE.equals(restPermission) && - !DSpaceRestPermission.DELETE.equals(restPermission) || - !StringUtils.equals(targetType, "AdminOrOwner")) { - return false; - } - - Request request = requestService.getCurrentRequest(); - Context context = ContextUtil.obtainContext(request.getHttpServletRequest()); - - UUID dsoId = UUID.fromString(targetId.toString()); - EPerson currentUser = context.getCurrentUser(); - - // anonymous user - if (Objects.isNull(currentUser)) { - return false; - } - - try { - return dsoId.equals(currentUser.getID()) || authorizeService.isAdmin(context, currentUser); - } catch (SQLException e) { - log.error(e.getMessage(), e); - } - return false; - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java new file mode 100644 index 000000000000..7062b1509f0e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java @@ -0,0 +1,75 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security; + +import java.sql.SQLException; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.utils.DSpaceObjectUtils; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.dspace.services.RequestService; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Methods of this class are used on PreAuthorize annotations + * to check security on subscriptions endpoint + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) + */ +@Component(value = "subscriptionSecurity") +public class SubscriptionSecurityBean { + + @Autowired + private EPersonService ePersonService; + @Autowired + private AuthorizeService authorizeService; + @Autowired + private DSpaceObjectUtils dspaceObjectUtil; + + public boolean isEnabelToCreateSubscription(Context context) throws SQLException { + RequestService requestService = new DSpace().getRequestService(); + HttpServletRequest req = requestService.getCurrentRequest().getHttpServletRequest(); + String epersonId = req.getParameter("eperson_id"); + String dsoId = req.getParameter("resource"); + + if (Objects.isNull(dsoId) || Objects.isNull(epersonId)) { + return true; + } + + try { + DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); + EPerson ePerson = ePersonService.findByIdOrLegacyId(context, epersonId); + if (Objects.isNull(ePerson) || Objects.isNull(dSpaceObject)) { + return true; + } + + if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, Constants.READ, true)) { + return false; + } + + if (!authorizeService.isAdmin(context)) { + if (!ePerson.equals(context.getCurrentUser())) { + return false; + } + } + } catch (SQLException sqlException) { + throw new SQLException(sqlException.getMessage(), sqlException); + } + return true; + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index 091ff5ce684d..d62f9b53d7ff 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -33,10 +33,8 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; -import org.dspace.builder.ItemBuilder; import org.dspace.builder.SubscribeBuilder; import org.dspace.content.Collection; -import org.dspace.content.Item; import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; @@ -44,8 +42,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; /** * Integration test to test the /api/config/subscriptions endpoint @@ -55,7 +51,7 @@ */ public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationTest { - private Item publicItem; + private Collection collection; @Before @@ -68,15 +64,9 @@ public void setUp() throws Exception { .withName("Parent Community") .build(); collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1") - .build(); - // creation of the item which will be the DSO related with a subscription - publicItem = ItemBuilder.createItem(context, collection) - .withTitle("Test") - .withIssueDate("2010-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry") - .build(); + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); context.restoreAuthSystemState(); } @@ -90,7 +80,7 @@ public void findAll() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); List subscriptionParameterList2 = new ArrayList<>(); SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); subscriptionParameter2.setName("frequency"); @@ -136,7 +126,7 @@ public void findOneWithOwnerTest() throws Exception { subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -168,7 +158,7 @@ public void findOneAdminTest() throws Exception { subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, admin, subscriptionParameterList).build(); + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -199,7 +189,7 @@ public void findOneAnonymousTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, admin, subscriptionParameterList).build(); + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/subscriptions/" + subscription.getID())) @@ -215,7 +205,7 @@ public void findOneForbiddenTest() throws Exception { subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, admin, subscriptionParameterList).build(); + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -239,7 +229,7 @@ public void findSubscriptionsByEPersonAdminTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); List subscriptionParameterList2 = new ArrayList<>(); SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); subscriptionParameter2.setName("frequency"); @@ -273,7 +263,7 @@ public void findSubscriptionsByEPersonOwnerTest() throws Exception { subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); List subscriptionParameterList2 = new ArrayList<>(); SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); subscriptionParameter2.setName("frequency"); @@ -306,7 +296,7 @@ public void findSubscriptionsByEPersonUnauthorizedTest() throws Exception { subscriptionParameter.setName("frequency"); subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - SubscribeBuilder.subscribeBuilder(context, "content", publicItem, eperson, subscriptionParameterList).build(); + SubscribeBuilder.subscribeBuilder(context, "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/subscriptions/search/findByEPerson") @@ -327,7 +317,7 @@ public void findSubscriptionsByEPersonForbiddenTest() throws Exception { subscriptionParameter.setName("frequency"); subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); - SubscribeBuilder.subscribeBuilder(context, "content", publicItem, user, subscriptionParameterList).build(); + SubscribeBuilder.subscribeBuilder(context, "content", collection, user, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -349,16 +339,12 @@ public void createSubscriptionUnauthorizedTest() throws Exception { SubscriptionRest subscriptionRest = new SubscriptionRest(); subscriptionRest.setSubscriptionType("content"); - MultiValueMap params = new LinkedMultiValueMap(); - params.add("resource", publicItem.getID().toString()); - params.add("eperson_id", eperson.getID().toString()); - context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); getClient().perform(post("/api/core/subscriptions") - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(subscriptionRest)) .contentType(contentType)) @@ -385,7 +371,7 @@ public void createSubscriptionAdminForOtherPersonTest() throws Exception { try { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(post("/api/core/subscriptions") - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -421,7 +407,7 @@ public void createSubscriptionByEPersonTest() throws Exception { try { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -454,7 +440,7 @@ public void createSubscriptionWithWrongSubscriptionParameterNameTest() throws Ex String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -478,7 +464,7 @@ public void createSubscriptionWithInvalidSubscriptionParameterValueTest() throws String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -502,7 +488,7 @@ public void createSubscriptionWithInvalidSubscriptionTypeValueTest() throws Exce String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -526,7 +512,7 @@ public void createSubscriptionInvalidJsonTest() throws Exception { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -555,7 +541,7 @@ public void createSubscriptionPersonForAnotherPersonTest() throws Exception { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", user.getID().toString()) .content(new ObjectMapper().writeValueAsString(map)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -571,7 +557,7 @@ public void deleteSubscriptionUnauthorizedTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(delete("/api/core/subscriptions/" + subscription.getID())) @@ -587,7 +573,7 @@ public void deleteSubscriptionAdminTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -607,7 +593,7 @@ public void deleteSubscriptionForbiddenTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, admin, subscriptionParameterList).build(); + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -631,7 +617,7 @@ public void putSubscriptionUnauthorizedTest() throws Exception { subscriptionParameter.setValue("ValueParameter1"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, admin, subscriptionParameterList).build(); + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); @@ -645,7 +631,7 @@ public void putSubscriptionUnauthorizedTest() throws Exception { newSubscription.put("subscriptionParameterList", list); getClient().perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", admin.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -661,7 +647,7 @@ public void putSubscriptionForbiddenTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, admin, subscriptionParameterList).build(); + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); @@ -677,7 +663,7 @@ public void putSubscriptionForbiddenTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(put("/api/core/subscriptions/" + subscription.getID()) .param("eperson_id", admin.getID().toString()) - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isForbidden()); @@ -692,7 +678,7 @@ public void putSubscriptionTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); @@ -707,7 +693,7 @@ public void putSubscriptionTest() throws Exception { String tokenSubscriber = getAuthToken(eperson.getEmail(), password); getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -729,7 +715,7 @@ public void putSubscriptionInvalidSubscriptionParameterNameTest() throws Excepti subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); @@ -744,7 +730,7 @@ public void putSubscriptionInvalidSubscriptionParameterNameTest() throws Excepti String tokenSubscriber = getAuthToken(eperson.getEmail(), password); getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -760,7 +746,7 @@ public void putSubscriptionInvalidSubscriptionParameterValueTest() throws Except subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); @@ -775,7 +761,7 @@ public void putSubscriptionInvalidSubscriptionParameterValueTest() throws Except String tokenSubscriber = getAuthToken(eperson.getEmail(), password); getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -791,7 +777,7 @@ public void putSubscriptionInvalidSubscriptionTypeValueTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); @@ -806,7 +792,7 @@ public void putSubscriptionInvalidSubscriptionTypeValueTest() throws Exception { String tokenSubscriber = getAuthToken(eperson.getEmail(), password); getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -822,7 +808,7 @@ public void putSubscriptionAdminTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); ObjectMapper objectMapper = new ObjectMapper(); @@ -837,7 +823,7 @@ public void putSubscriptionAdminTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", publicItem.getID().toString()) + .param("resource", collection.getID().toString()) .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -859,7 +845,7 @@ public void linkedEpersonOfSubscriptionAdminTest() throws Exception { subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); @@ -877,7 +863,7 @@ public void linkedEpersonOfSubscriptionTest() throws Exception { subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -895,7 +881,7 @@ public void linkedEpersonOfSubscriptionUnauthorizedTest() throws Exception { subscriptionParameter.setValue("M"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/eperson")) @@ -911,7 +897,7 @@ public void linkedEpersonOfSubscriptionForbiddenTest() throws Exception { subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, admin, subscriptionParameterList).build(); + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); @@ -935,18 +921,15 @@ public void linkedDSpaceObjectOfSubscriptionAdminTest() throws Exception { subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.uuid", Matchers.is(publicItem.getID().toString()))) - .andExpect(jsonPath("$.name", Matchers.is(publicItem.getName()))) - .andExpect(jsonPath("$.withdrawn", Matchers.is(false))) - .andExpect(jsonPath("$.discoverable", Matchers.is(true))) - .andExpect(jsonPath("$.inArchive", Matchers.is(true))) - .andExpect(jsonPath("$.type", Matchers.is("item"))); + .andExpect(jsonPath("$.uuid", Matchers.is(collection.getID().toString()))) + .andExpect(jsonPath("$.name", Matchers.is(collection.getName()))) + .andExpect(jsonPath("$.type", Matchers.is("collection"))); } @Test @@ -958,18 +941,15 @@ public void linkedDSpaceObjectOfSubscriptionTest() throws Exception { subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenAdmin = getAuthToken(eperson.getEmail(), password); getClient(tokenAdmin).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.uuid", Matchers.is(publicItem.getID().toString()))) - .andExpect(jsonPath("$.name", Matchers.is(publicItem.getName()))) - .andExpect(jsonPath("$.withdrawn", Matchers.is(false))) - .andExpect(jsonPath("$.discoverable", Matchers.is(true))) - .andExpect(jsonPath("$.inArchive", Matchers.is(true))) - .andExpect(jsonPath("$.type", Matchers.is("item"))); + .andExpect(jsonPath("$.uuid", Matchers.is(collection.getID().toString()))) + .andExpect(jsonPath("$.name", Matchers.is(collection.getName()))) + .andExpect(jsonPath("$.type", Matchers.is("collection"))); } @Test @@ -981,7 +961,7 @@ public void linkedDSpaceObjectOfSubscriptionUnauthorizedTest() throws Exception subscriptionParameter.setValue("W"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, eperson, subscriptionParameterList).build(); + "content", collection, eperson, subscriptionParameterList).build(); context.restoreAuthSystemState(); getClient().perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) @@ -997,7 +977,7 @@ public void linkedDSpaceObjectOfSubscriptionForbiddenTest() throws Exception { subscriptionParameter.setValue("D"); subscriptionParameterList.add(subscriptionParameter); Subscription subscription = SubscribeBuilder.subscribeBuilder(context, - "content", publicItem, admin, subscriptionParameterList).build(); + "content", collection, admin, subscriptionParameterList).build(); context.restoreAuthSystemState(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index d03a30ac3552..e7f071409933 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -26,22 +26,10 @@ - - - - - - - - D - W - M - - @@ -51,7 +39,6 @@ - @@ -76,6 +63,4 @@ - - From 80a7119344f8f9a47d33a75631dc6a7ba234f626 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 2 Feb 2023 19:31:13 +0100 Subject: [PATCH 223/394] [CST-7756] minor fix --- .../java/org/dspace/eperson/SubscribeServiceTest.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java index c743969f012e..3526af717051 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java @@ -250,8 +250,8 @@ public void updateSubscription() throws Exception { getSubscriptionParameter("frequency", "A") ); - Subscription updated = subscribeService.updateSubscription(context, subscriptionId, null, null, - updatedParameters, updatedType); + Subscription updated = subscribeService.updateSubscription(context, subscriptionId, updatedType, + updatedParameters); ArgumentCaptor captor = ArgumentCaptor.forClass(Subscription.class); verify(subscriptionDAO).save(eq(context), captor.capture()); @@ -282,9 +282,7 @@ public void errorWhileUpdating() throws Exception { getSubscriptionParameter("frequency", "A") ); - subscribeService.updateSubscription(context, subscriptionId, null, null, - updatedParameters, updatedType); - + subscribeService.updateSubscription(context, subscriptionId, updatedType, updatedParameters); } @Test From 611be0048178d05868c03e652f6c7969d55df912 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 2 Feb 2023 23:58:51 +0100 Subject: [PATCH 224/394] [CST-7756] extended SubscribeServiceTest in order to use service instance with real (h2) database instead of mocks, so that tests actually store, query and remove subscriptions interacting with database --- .../dspace/eperson/SubscribeServiceTest.java | 547 +++++++++--------- .../org/dspace/matcher/SubscribeMatcher.java | 79 +++ 2 files changed, 364 insertions(+), 262 deletions(-) create mode 100644 dspace-api/src/test/java/org/dspace/matcher/SubscribeMatcher.java diff --git a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java index c743969f012e..6e08cd97d7d6 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java @@ -8,387 +8,410 @@ package org.dspace.eperson; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.dspace.builder.SubscribeBuilder.subscribeBuilder; +import static org.dspace.matcher.SubscribeMatcher.matches; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.UUID; +import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; -import org.dspace.AbstractDSpaceTest; +import org.apache.commons.lang.StringUtils; +import org.dspace.AbstractIntegrationTestWithDatabase; import org.dspace.authorize.AuthorizeException; -import org.dspace.authorize.service.AuthorizeService; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.SubscribeBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Community; import org.dspace.content.DSpaceObject; -import org.dspace.core.Context; -import org.dspace.eperson.dao.SubscriptionDAO; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.eperson.service.SubscribeService; +import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -public class SubscribeServiceTest extends AbstractDSpaceTest { +public class SubscribeServiceTest extends AbstractIntegrationTestWithDatabase { - @Mock - private SubscriptionDAO subscriptionDAO; + private final SubscribeService subscribeService = ContentServiceFactory.getInstance().getSubscribeService(); - @Mock - private AuthorizeService authorizeService; + private Collection firstCollection; + private Collection secondCollection; - @InjectMocks - private SubscribeServiceImpl subscribeService; - - private final Context context = Mockito.mock(Context.class); + @Before + public void init() throws Exception { + context.turnOffAuthorisationSystem(); + Community parentCommunity = CommunityBuilder.createCommunity(context).build(); + firstCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("First Collection").build(); + secondCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Second Collection").build(); + context.restoreAuthSystemState(); + } @Test - public void findAllWithValidResource() throws Exception { + public void findAllWithoutAndWithLimit() throws Exception { - String resourceType = "Item"; - Integer limit = 10; - Integer offset = 0; + String resourceType = "Collection"; - Subscription subscription = createSubscription("content", - getSubscriptionParameter("frequency", "W")); + EPerson subscribingUser = context.getCurrentUser(); - when(subscriptionDAO.findAllOrderedByIDAndResourceType(context, resourceType, limit, offset)) - .thenReturn(Collections.singletonList(subscription)); + createSubscription("content", firstCollection, subscribingUser, weekly()); + createSubscription("content", secondCollection, subscribingUser, daily(), annual()); - List subscriptions = subscribeService.findAll(context, resourceType, limit, offset); + // unlimited search returns all subscriptions - assertEquals(subscriptions, Collections.singletonList(subscription)); + List subscriptions = subscribeService.findAll(context, resourceType, 10, 0); + assertThat(subscriptions, containsInAnyOrder( + asList(matches(firstCollection, subscribingUser, "content", + singletonList(weekly())), + matches(secondCollection, subscribingUser, "content", + asList(daily(), annual()))))); - } + // limited search returns first - @Test - public void findAllWithoutResourceType() throws Exception { - String resourceType = StringUtils.EMPTY; - Integer limit = 10; - Integer offset = 0; + subscriptions = subscribeService.findAll(context, resourceType, 1, 0); + + assertThat(subscriptions, containsInAnyOrder( + singletonList(matches(firstCollection, subscribingUser, "content", + singletonList(weekly()))))); + + // search with offset returns second + + subscriptions = subscribeService.findAll(context, resourceType, 100, 1); - Subscription subscription = createSubscription("content", - getSubscriptionParameter("frequency", "W")); + assertThat(subscriptions, containsInAnyOrder( + singletonList(matches(secondCollection, subscribingUser, "content", + asList(daily(), annual()))))); - when(subscriptionDAO.findAllOrderedByDSO(context, limit, offset)) - .thenReturn(Collections.singletonList(subscription)); + // lookup without resource type + subscriptions = subscribeService.findAll(context, StringUtils.EMPTY, 100, 0); - List subscriptions = subscribeService.findAll(context, resourceType, limit, offset); + assertThat(subscriptions, containsInAnyOrder( + asList(matches(firstCollection, subscribingUser, "content", + singletonList(weekly())), + matches(secondCollection, subscribingUser, "content", + asList(daily(), annual()))))); - assertEquals(subscriptions, Collections.singletonList(subscription)); + } + + private static SubscriptionParameter annual() { + return createSubscriptionParameter("frequency", "A"); + } + + private static SubscriptionParameter daily() { + return createSubscriptionParameter("frequency", "D"); } @Test(expected = Exception.class) - public void findAllWithInValidResource() throws Exception { + public void findAllWithInvalidResource() throws Exception { String resourceType = "INVALID"; Integer limit = 10; Integer offset = 0; - subscribeService.findAll(context, resourceType, limit, offset); + createSubscription("content", firstCollection, context.getCurrentUser(), + weekly()); - verifyNoInteractions(subscriptionDAO); + subscribeService.findAll(context, resourceType, limit, offset); } @Test public void newSubscriptionCreatedByAdmin() throws Exception { - Subscription dbSubscription = createSubscription("content", - getSubscriptionParameter("frequency", "W")); - - when(authorizeService.isAdmin(context)).thenReturn(true); - when(subscriptionDAO.create(eq(context), any(Subscription.class))) - .thenReturn(dbSubscription); - DSpaceObject dso = mock(DSpaceObject.class); + SubscriptionParameter monthly = createSubscriptionParameter("frequency", "M"); List parameters = Collections.singletonList( - getSubscriptionParameter("frequency", "M")); + monthly); + + EPerson currentUser = context.getCurrentUser(); + context.setCurrentUser(admin); + Subscription subscription = subscribeService.subscribe(context, eperson, + firstCollection, parameters, "content"); + + assertThat(subscription, is(matches(firstCollection, eperson, + "content", singletonList(monthly)))); - Subscription subscription = subscribeService.subscribe(context, getePerson(), - dso, parameters, "content"); + SubscribeBuilder.deleteSubscription(subscription.getID()); + context.setCurrentUser(currentUser); - assertEquals(subscription, dbSubscription); } @Test public void newSubscriptionCreatedByCurrentUser() throws Exception { - Subscription dbSubscription = createSubscription("content", - getSubscriptionParameter("frequency", "W")); - EPerson currentUser = getePerson(); - - when(context.getCurrentUser()).thenReturn(currentUser); - - when(subscriptionDAO.create(eq(context), any(Subscription.class))) - .thenReturn(dbSubscription); - DSpaceObject dso = mock(DSpaceObject.class); - - List parameters = Collections.singletonList( - getSubscriptionParameter("frequency", "D")); - + EPerson currentUser = context.getCurrentUser(); Subscription subscription = subscribeService.subscribe(context, currentUser, - dso, parameters, "content"); + secondCollection, + asList(daily(), weekly()), "content"); - assertEquals(subscription, dbSubscription); + assertThat(subscription, matches(secondCollection, currentUser, "content", + asList(daily(), weekly()))); + + SubscribeBuilder.deleteSubscription(subscription.getID()); } @Test(expected = AuthorizeException.class) public void nonAdminDifferentUserTriesToSubscribe() throws Exception { + context.turnOffAuthorisationSystem(); + EPerson notAdmin = EPersonBuilder.createEPerson(context).withEmail("not-admin@example.com").build(); + context.restoreAuthSystemState(); + EPerson currentUser = context.getCurrentUser(); + context.setCurrentUser(notAdmin); + try { + subscribeService.subscribe(context, admin, firstCollection, + singletonList( + daily()), "content"); + } finally { + context.setCurrentUser(currentUser); + } - EPerson currentUser = getePerson(); - EPerson subscribingUser = getePerson(); - - when(context.getCurrentUser()).thenReturn(currentUser); - - DSpaceObject dso = mock(DSpaceObject.class); - - List parameters = Collections.singletonList( - getSubscriptionParameter("frequency", "D")); - - subscribeService.subscribe(context, subscribingUser,dso, parameters, "content"); - - verifyNoInteractions(subscriptionDAO); } - @Test(expected = SQLException.class) - public void exceptionWhileStoringSubscription() throws Exception { - EPerson currentUser = getePerson(); - - when(context.getCurrentUser()).thenReturn(currentUser); + @Test + public void unsubscribeByAdmin() throws Exception { - doThrow(new SQLException()).when(subscriptionDAO).create(eq(context), any(Subscription.class)); + EPerson subscribingUser = context.getCurrentUser(); + createSubscription("content", secondCollection, subscribingUser, + weekly()); - DSpaceObject dso = mock(DSpaceObject.class); + List subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, subscribingUser, + secondCollection, 100, 0); - List parameters = Collections.singletonList( - getSubscriptionParameter("frequency", "D")); + assertEquals(subscriptions.size(), 1); - subscribeService.subscribe(context, currentUser,dso, parameters, "content"); + context.setCurrentUser(admin); + subscribeService.unsubscribe(context, subscribingUser, secondCollection); + context.setCurrentUser(subscribingUser); - } + subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, subscribingUser, + secondCollection, 100, 0); - @Test - public void unsubscribeByAdmin() throws Exception { - - when(authorizeService.isAdmin(context)).thenReturn(true); - DSpaceObject dso = mock(DSpaceObject.class); - - EPerson currentUser = getePerson(); - subscribeService.unsubscribe(context, currentUser, - dso); - verify(subscriptionDAO).deleteByDSOAndEPerson(context, dso, currentUser); + assertEquals(subscriptions.size(), 0); } @Test - public void unsubscribeByCurrentUser() throws Exception { + public void subscribingUserUnsubscribesTheirSubscription() throws Exception { - EPerson currentUser = getePerson(); - when(authorizeService.isAdmin(context)).thenReturn(false); - when(context.getCurrentUser()).thenReturn(currentUser); + EPerson subscribingUser = context.getCurrentUser(); + createSubscription("content", secondCollection, subscribingUser, + weekly()); - DSpaceObject dso = mock(DSpaceObject.class); + List subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, subscribingUser, + secondCollection, 100, 0); - subscribeService.unsubscribe(context, currentUser, dso); - verify(subscriptionDAO).deleteByDSOAndEPerson(context, dso, currentUser); - } - - @Test(expected = AuthorizeException.class) - public void nonAdminDifferentUserTriesToUnSubscribe() throws Exception { - EPerson currentUser = getePerson(); - EPerson unsubscribingUser = getePerson(); + assertEquals(subscriptions.size(), 1); - when(authorizeService.isAdmin(context)).thenReturn(false); - when(context.getCurrentUser()).thenReturn(currentUser); - DSpaceObject dso = mock(DSpaceObject.class); + subscribeService.unsubscribe(context, subscribingUser, secondCollection); - subscribeService.unsubscribe(context, unsubscribingUser, dso); + subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, subscribingUser, + secondCollection, 100, 0); - verifyNoInteractions(subscriptionDAO); + assertEquals(subscriptions.size(), 0); } - @Test(expected = SQLException.class) - public void exceptionWhileDeletingSubscription() throws Exception { - EPerson currentUser = getePerson(); - - when(context.getCurrentUser()).thenReturn(currentUser); + @Test(expected = AuthorizeException.class) + public void nonAdminDifferentUserTriesToUnSubscribeAnotherUser() throws Exception { + EPerson subscribingUser = context.getCurrentUser(); + Subscription subscription = createSubscription("content", secondCollection, subscribingUser, + weekly()); - DSpaceObject dso = mock(DSpaceObject.class); + context.turnOffAuthorisationSystem(); + EPerson nonAdmin = EPersonBuilder.createEPerson(context).build(); + context.restoreAuthSystemState(); - doThrow(new SQLException()).when(subscriptionDAO).deleteByDSOAndEPerson(eq(context), eq(dso), - eq(currentUser)); - subscribeService.unsubscribe(context, currentUser,dso); + try { + context.setCurrentUser(nonAdmin); + subscribeService.unsubscribe(context, subscribingUser, secondCollection); + } finally { + context.setCurrentUser(subscribingUser); + SubscribeBuilder.deleteSubscription(subscription.getID()); + } } @Test public void updateSubscription() throws Exception { - - int subscriptionId = 434123; - - Subscription subscription = createSubscription("original", getSubscriptionParameter("frequency", "D"), - getSubscriptionParameter("frequency", "M")); - - when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) - .thenReturn(subscription); + EPerson currentUser = context.getCurrentUser(); + Subscription subscription = createSubscription("original", + firstCollection, currentUser, + createSubscriptionParameter("frequency", "M")); String updatedType = "updated"; List updatedParameters = Collections.singletonList( - getSubscriptionParameter("frequency", "A") + annual() ); - Subscription updated = subscribeService.updateSubscription(context, subscriptionId, null, null, - updatedParameters, updatedType); - - ArgumentCaptor captor = ArgumentCaptor.forClass(Subscription.class); - verify(subscriptionDAO).save(eq(context), captor.capture()); - - assertEquals(captor.getValue().getSubscriptionParameterList(), updatedParameters); - assertEquals(captor.getValue().getSubscriptionType(), updatedType); - - assertEquals(updated.getSubscriptionType(), updatedType); - assertEquals(updated.getSubscriptionParameterList(), updatedParameters); - - } - - @Test(expected = SQLException.class) - public void errorWhileUpdating() throws Exception { + try { + Subscription updated = subscribeService.updateSubscription(context, subscription.getID(), + updatedType, updatedParameters); + assertThat(updated, is(matches(firstCollection, currentUser, updatedType, updatedParameters))); - int subscriptionId = 434; + List subscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, currentUser, firstCollection, 10, 0); - Subscription subscription = createSubscription("original", getSubscriptionParameter("frequency", "D"), - getSubscriptionParameter("frequency", "M")); + assertThat(subscriptions, contains( + matches(firstCollection, currentUser, updatedType, updatedParameters))); - when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) - .thenReturn(subscription); - doThrow(new SQLException()).when(subscriptionDAO).save(eq(context), any(Subscription.class)); - - String updatedType = "updated"; - List updatedParameters = Collections.singletonList( - getSubscriptionParameter("frequency", "A") - ); - - subscribeService.updateSubscription(context, subscriptionId, null, null, - updatedParameters, updatedType); + } finally { + SubscribeBuilder.deleteSubscription(subscription.getID()); + } } @Test public void parametersAdditionAndRemoval() throws Exception { - int subscriptionId = 43123; - - SubscriptionParameter firstParameter = getSubscriptionParameter("key1", "value1"); - SubscriptionParameter secondParameter = getSubscriptionParameter("key2", "value2"); + SubscriptionParameter firstParameter = createSubscriptionParameter("key1", "value1"); + SubscriptionParameter secondParameter = createSubscriptionParameter("key2", "value2"); - Subscription existingSubscription = createSubscription("type", + EPerson currentUser = context.getCurrentUser(); + Subscription subscription = createSubscription("type", secondCollection, currentUser, firstParameter, secondParameter); - - when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) - .thenReturn(existingSubscription); - - SubscriptionParameter addedParameter = getSubscriptionParameter("added", "add"); - - Subscription updatedSubscription = subscribeService.addSubscriptionParameter(context, subscriptionId, - addedParameter); - - assertParametersAreUpdated(updatedSubscription, List.of(firstParameter, secondParameter, - addedParameter)); - - updatedSubscription = subscribeService.removeSubscriptionParameter(context, subscriptionId, - secondParameter); - - assertParametersAreUpdated(updatedSubscription, List.of(firstParameter, addedParameter)); - - + int subscriptionId = subscription.getID(); + + SubscriptionParameter addedParameter = createSubscriptionParameter("added", "add"); + + + try { + Subscription updatedSubscription = subscribeService.addSubscriptionParameter(context, subscriptionId, + addedParameter); + assertThat(updatedSubscription, is(matches(secondCollection, currentUser, "type", + asList(firstParameter, secondParameter, addedParameter)))); + updatedSubscription = subscribeService.removeSubscriptionParameter(context, subscriptionId, + secondParameter); + assertThat(updatedSubscription, is(matches(secondCollection, currentUser, "type", + asList(firstParameter, addedParameter)))); + } finally { + SubscribeBuilder.deleteSubscription(subscriptionId); + } } - @Test(expected = SQLException.class) - public void exceptionWhileAddingParameters() throws Exception { - int subscriptionId = 43123; - - SubscriptionParameter firstParameter = getSubscriptionParameter("key1", "value1"); - SubscriptionParameter secondParameter = getSubscriptionParameter("key2", "value2"); - - Subscription existingSubscription = createSubscription("type", - firstParameter, secondParameter); - - when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) - .thenReturn(existingSubscription); - + @Test + public void findersAndDeletionsTest() throws SQLException { + // method to test all find and delete methods exposed by SubscribeService + context.turnOffAuthorisationSystem(); + EPerson firstSubscriber = EPersonBuilder.createEPerson(context).withEmail("first-user@example.com").build(); + EPerson secondSubscriber = EPersonBuilder.createEPerson(context).withEmail("second-user@example.com").build(); + EPerson thirdSubscriber = EPersonBuilder.createEPerson(context).withEmail("third-user@example.com").build(); + context.restoreAuthSystemState(); + + EPerson currentUser = context.getCurrentUser(); + try { + context.setCurrentUser(firstSubscriber); + createSubscription("type1", firstCollection, firstSubscriber, daily(), + weekly()); + createSubscription("type1", secondCollection, firstSubscriber, + daily(), + annual()); + createSubscription("type2", secondCollection, firstSubscriber, + daily()); + + context.setCurrentUser(secondSubscriber); + createSubscription("type1", firstCollection, secondSubscriber, + daily()); + createSubscription("type1", secondCollection, secondSubscriber, + daily(), + annual()); + + context.setCurrentUser(thirdSubscriber); + createSubscription("type1", firstCollection, thirdSubscriber, daily()); + createSubscription("type1", secondCollection, thirdSubscriber, + daily(), + annual()); + + } finally { + context.setCurrentUser(currentUser); + } + + List firstUserSubscriptions = + subscribeService.findSubscriptionsByEPerson(context, firstSubscriber, 100, 0); + + assertThat(firstUserSubscriptions, containsInAnyOrder( + matches(firstCollection, firstSubscriber, "type1", asList(daily(), + weekly())), + matches(secondCollection, firstSubscriber, "type1", asList(daily(), + annual())), + matches(secondCollection, firstSubscriber, "type2", singletonList( + daily())) + )); + + List firstUserSubscriptionsLimited = + subscribeService.findSubscriptionsByEPerson(context, firstSubscriber, 1, 0); + + assertThat(firstUserSubscriptionsLimited.size(), is(1)); + + List firstUserSubscriptionsWithOffset = + subscribeService.findSubscriptionsByEPerson(context, firstSubscriber, 100, 1); + + assertThat(firstUserSubscriptionsWithOffset.size(), is(2)); + + subscribeService.deleteByEPerson(context, firstSubscriber); + assertThat(subscribeService.findSubscriptionsByEPerson(context, firstSubscriber, 100, 0), + is(List.of())); + + List secondSubscriberSecondCollectionSubscriptions = + subscribeService.findSubscriptionsByEPersonAndDso(context, secondSubscriber, firstCollection, 10, 0); + + assertThat(secondSubscriberSecondCollectionSubscriptions, contains( + matches(firstCollection, secondSubscriber, "type1", singletonList(daily())) + )); + + List byTypeAndFrequency = + subscribeService.findAllSubscriptionsBySubscriptionTypeAndFrequency(context, "type1", + "D"); + assertThat(byTypeAndFrequency, containsInAnyOrder( + matches(firstCollection, secondSubscriber, "type1", singletonList( + daily())), + matches(secondCollection, secondSubscriber, "type1", asList(daily(), + annual())), + matches(firstCollection, thirdSubscriber, "type1", singletonList( + daily())), + matches(secondCollection, thirdSubscriber, "type1", asList(daily(), + annual())) + )); + + assertThat(subscribeService.countAll(context), is(4L)); + assertThat(subscribeService.countByEPersonAndDSO(context, secondSubscriber, secondCollection), is(1L)); + assertThat(subscribeService.countSubscriptionsByEPerson(context, thirdSubscriber), is(2L)); - doThrow(new SQLException()).when(subscriptionDAO).save(eq(context), any(Subscription.class)); - subscribeService.addSubscriptionParameter(context, subscriptionId, - getSubscriptionParameter("added", "add")); } - @Test(expected = SQLException.class) - public void exceptionWhileRemovingParameters() throws Exception { - int subscriptionId = 43127; - - SubscriptionParameter firstParameter = getSubscriptionParameter("key1", "value1"); - SubscriptionParameter secondParameter = getSubscriptionParameter("key2", "value2"); - - Subscription existingSubscription = createSubscription("type", - firstParameter, secondParameter); - - when(subscriptionDAO.findByID(context, Subscription.class, subscriptionId)) - .thenReturn(existingSubscription); - - - doThrow(new SQLException()).when(subscriptionDAO).save(eq(context), any(Subscription.class)); - - subscribeService.removeSubscriptionParameter(context, subscriptionId, - secondParameter); + private static SubscriptionParameter weekly() { + return createSubscriptionParameter("frequency", "W"); } - private static Subscription createSubscription(String type, SubscriptionParameter... parameters) { - Subscription subscription = new Subscription(); - subscription.setSubscriptionType(type); - - subscription.setSubscriptionParameterList - (new ArrayList<>(Arrays.asList(parameters))); - return subscription; + private Subscription createSubscription(String type, DSpaceObject dso, EPerson ePerson, + SubscriptionParameter... parameters) { + return subscribeBuilder(context, type, + dso, ePerson, + Arrays.stream(parameters).collect(Collectors.toList())).build(); } - private void assertParametersAreUpdated(Subscription updatedSubscription, - List expectedParameters) - throws SQLException { - ArgumentCaptor captor = ArgumentCaptor.forClass(Subscription.class); - verify(subscriptionDAO, atLeastOnce()).save(eq(context), captor.capture()); - - - assertEquals(captor.getValue().getSubscriptionParameterList(), expectedParameters); - assertEquals(updatedSubscription.getSubscriptionParameterList(), expectedParameters); - } - - private static SubscriptionParameter getSubscriptionParameter(String name, String value) { + private static SubscriptionParameter createSubscriptionParameter(String name, String value) { SubscriptionParameter parameter = new SubscriptionParameter(); parameter.setName(name); parameter.setValue(value); return parameter; } - private static EPerson getePerson() { - EPerson ePerson = mock(EPerson.class); - UUID uuid = UUID.randomUUID(); - doReturn(uuid).when(ePerson).getID(); - return ePerson; - } } \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/matcher/SubscribeMatcher.java b/dspace-api/src/test/java/org/dspace/matcher/SubscribeMatcher.java new file mode 100644 index 000000000000..4671e65d3875 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/matcher/SubscribeMatcher.java @@ -0,0 +1,79 @@ +/** + * 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.matcher; + +import java.util.List; +import java.util.stream.Collectors; + +import org.dspace.content.DSpaceObject; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Subscription; +import org.dspace.eperson.SubscriptionParameter; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +public class SubscribeMatcher extends BaseMatcher { + + private final DSpaceObject dso; + private final EPerson eperson; + private final List parameters; + private final String type; + + private SubscribeMatcher(DSpaceObject dso, EPerson eperson, String type, List parameters) { + this.dso = dso; + this.eperson = eperson; + this.parameters = parameters; + this.type = type; + } + + public static SubscribeMatcher matches(DSpaceObject dso, EPerson ePerson, String type, + List parameters) { + return new SubscribeMatcher(dso, ePerson, type, parameters); + } + + @Override + public boolean matches(Object subscription) { + Subscription s = (Subscription) subscription; + return s.getEPerson().equals(eperson) + && s.getDSpaceObject().equals(dso) + && s.getSubscriptionType().equals(type) + && checkParameters(s.getSubscriptionParameterList()); + } + + private Boolean checkParameters(List parameters) { + if (parameters.size() != this.parameters.size()) { + return false; + } + // FIXME: for check purpose we rely on name and value. Evaluate to extend or refactor this part + for (int i = 0; i < parameters.size(); i++) { + SubscriptionParameter parameter = parameters.get(i); + SubscriptionParameter match = this.parameters.get(i); + boolean differentName = !parameter.getName().equals((match.getName())); + if (differentName) { + return false; + } + boolean differentValue = !parameter.getValue().equals((match.getValue())); + if (differentValue) { + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + String subscription = String.format("Type: %s, eperson: %s, dso: %s, params: %s", + type, eperson.getID(), dso.getID(), parameters.stream() + .map(p -> "{ name: " + p.getName() + + ", value: " + p.getValue() + + "}") + .collect(Collectors.joining(", "))); + description.appendText("Subscription matching: " + subscription); + } +} From 4fd8eeebb88b0df6026eed7b985cf139ccc83e4f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 3 Feb 2023 12:14:28 +0100 Subject: [PATCH 225/394] [CST-7756] added missing JavaDoc --- .../dspace/eperson/dao/SubscriptionDAO.java | 2 +- .../eperson/service/SubscribeService.java | 23 +++++++++++-------- ...riptionEmailNotificationConfiguration.java | 4 ++-- .../SubscriptionEmailNotificationService.java | 15 +++++++++++- ...criptionRestPermissionEvaluatorPlugin.java | 4 ++-- .../rest/SubscriptionRestRepositoryIT.java | 1 - 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java index e1c589077eef..4d762c1775dd 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java +++ b/dspace-api/src/main/java/org/dspace/eperson/dao/SubscriptionDAO.java @@ -85,7 +85,7 @@ public List findByEPersonAndDso(Context context, EPerson eperson, * Return a paginated list of all subscriptions ordered by ID and resourceType * * @param context DSpace context object - * @param resourceType Could be Item, Collection or Community + * @param resourceType Could be Collection or Community * @param limit Paging limit * @param offset The position of the first result to return * @return diff --git a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java index fa35e9992222..e70f40e0edf0 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java +++ b/dspace-api/src/main/java/org/dspace/eperson/service/SubscribeService.java @@ -42,17 +42,22 @@ public List findAll(Context context, String resourceType, Integer throws Exception; /** - * Subscribe an e-person to a collection. An e-mail will be sent every day a - * new item appears in the collection. - * - * @param context DSpace context - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. + * Subscribe an EPerson to a dSpaceObject (Collection or Community). An e-mail will be sent every day a + * new item appears in the Collection or Community. + * + * @param context DSpace context object + * @param eperson EPerson to subscribe + * @param dSpaceObject DSpaceObject to subscribe + * @param subscriptionParameters list of @SubscriptionParameter + * @param subscriptionType Currently supported only "content" + * @return + * @throws SQLException An exception that provides information on a database access error or other errors. + * @throws AuthorizeException Exception indicating the current user of the context does not have permission + * to perform a particular action. */ public Subscription subscribe(Context context, EPerson eperson, DSpaceObject dSpaceObject, - List subscriptionParameterList, - String type) throws SQLException, AuthorizeException; + List subscriptionParameters, + String subscriptionType) throws SQLException, AuthorizeException; /** * Unsubscribe an e-person to a collection. Passing in null diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java index 1bd39bb765b4..542eae6fa75c 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java @@ -42,8 +42,8 @@ public boolean isAllowedToExecute(Context context) { public Options getOptions() { if (Objects.isNull(options)) { Options options = new Options(); - options.addOption("f", "Frequency", true, "Subscription frequency." - + " It can have value D stand for 'Day', W stand for 'Week' and M stand for 'Month'"); + options.addOption("f", "Frequency", true, + "Subscription frequency. Valid values include: D (Day), W (Week) and M (Month)"); options.getOption("f").setRequired(true); super.options = options; } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java index 5cf7c09a0a76..95272235095a 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationService.java @@ -13,12 +13,25 @@ import org.dspace.scripts.handler.DSpaceRunnableHandler; /** + * Service interface class for the subscription e-mail notification services + * * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) */ public interface SubscriptionEmailNotificationService { - public void perform(Context context, DSpaceRunnableHandler handler, String type, String frequency); + /** + * Performs sending of e-mails to subscribers by frequency value and SubscriptionType + * + * @param context DSpace context object + * @param handler Applicable DSpaceRunnableHandler + * @param subscriptionType Currently supported only "content" + * @param frequency Valid values include: D (Day), W (Week) and M (Month) + */ + public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency); + /** + * returns a set of supported SubscriptionTypes + */ public Set getSupportedSubscriptionTypes(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java index 0f637d186a5f..c93d966e73cb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionRestPermissionEvaluatorPlugin.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Component; /** - * {@link RestPermissionEvaluatorPlugin} + * {@link RestPermissionEvaluatorPlugin} class that evaluate READ, WRITE and DELETE permissions over a Subscription * * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) */ @@ -74,7 +74,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } Subscription subscription = subscribeService.findById(context, Integer.parseInt(targetId.toString())); - return Objects.nonNull(subscription) ? currentUser.equals(subscription.getEPerson()) : true; + return Objects.nonNull(subscription) ? currentUser.equals(subscription.getEPerson()) : false; } catch (SQLException e) { log.error(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index d62f9b53d7ff..4a214645278e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -51,7 +51,6 @@ */ public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationTest { - private Collection collection; @Before From 669442eb03604fd15842641ae9355ac0411e8327 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 3 Feb 2023 15:08:26 +0100 Subject: [PATCH 226/394] [CST-7756] code refactoring --- .../main/java/org/dspace/subscriptions/ContentGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index c35729ad57bc..9b3f8cd185e9 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -75,7 +75,7 @@ private String generateBodyMail(Context context, List indexable for (IndexableObject indexableObject : indexableObjects) { out.write("\n".getBytes(UTF_8)); Item item = (Item) indexableObject.getIndexedObject(); - var entityType = itemService.getEntityTypeLabel(item); + String entityType = itemService.getEntityTypeLabel(item); mapEntityDisseminatorProperty.get(entityType).disseminate(context, item, out); } return out.toString(); From dca79c1a203abae3ee707bef0d845178af723a0f Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 3 Feb 2023 15:09:02 +0100 Subject: [PATCH 227/394] [CST-7756] renamed test class in order to reflect its actual behavior --- .../{SubscribeServiceTest.java => SubscribeServiceIT.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename dspace-api/src/test/java/org/dspace/eperson/{SubscribeServiceTest.java => SubscribeServiceIT.java} (99%) diff --git a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java similarity index 99% rename from dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java rename to dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java index 6e08cd97d7d6..945dd481d00a 100644 --- a/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/eperson/SubscribeServiceIT.java @@ -39,7 +39,7 @@ import org.junit.Before; import org.junit.Test; -public class SubscribeServiceTest extends AbstractIntegrationTestWithDatabase { +public class SubscribeServiceIT extends AbstractIntegrationTestWithDatabase { private final SubscribeService subscribeService = ContentServiceFactory.getInstance().getSubscribeService(); From ad6d514e0ef7867d219c86cef95888e9b0b6baf1 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 3 Feb 2023 15:09:45 +0100 Subject: [PATCH 228/394] [CST-7756] removed mail template used by previous script --- dspace/config/emails/subscription | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 dspace/config/emails/subscription diff --git a/dspace/config/emails/subscription b/dspace/config/emails/subscription deleted file mode 100644 index 2879e579075d..000000000000 --- a/dspace/config/emails/subscription +++ /dev/null @@ -1,12 +0,0 @@ -## E-mail sent to DSpace users when new items appear in collections they are -## subscribed to -## -## Parameters: {0} is the details of the new collections and items -## See org.dspace.core.Email for information on the format of this file. -## -#set($subject = 'DSpace Subscription') -New items are available in the collections you have subscribed to: - -${params[0]} - -DSpace From 2c2c037ce0c04747bf8ba852327bab5f6b60bff8 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 3 Feb 2023 15:10:27 +0100 Subject: [PATCH 229/394] [CST-7756] code refactoring --- .../subscriptions/SubscriptionEmailNotification.java | 2 +- .../SubscriptionEmailNotificationConfiguration.java | 2 +- .../SubscriptionEmailNotificationServiceImpl.java | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java index 9be4816fe87b..b429ecbd46e7 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotification.java @@ -50,7 +50,7 @@ public void internalRun() throws Exception { assignSpecialGroupsInContext(); String frequencyOption = commandLine.getOptionValue("f"); if (StringUtils.isBlank(frequencyOption)) { - throw new IllegalArgumentException("Option frequency f must be set"); + throw new IllegalArgumentException("Option --frequency (-f) must be set"); } if (!FrequencyType.isSupportedFrequencyType(frequencyOption)) { diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java index 542eae6fa75c..52685b563d9b 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationConfiguration.java @@ -42,7 +42,7 @@ public boolean isAllowedToExecute(Context context) { public Options getOptions() { if (Objects.isNull(options)) { Options options = new Options(); - options.addOption("f", "Frequency", true, + options.addOption("f", "frequency", true, "Subscription frequency. Valid values include: D (Day), W (Week) and M (Month)"); options.getOption("f").setRequired(true); super.options = options; diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java index 1db4672422c4..81648a92e3ef 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -72,11 +72,15 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc DSpaceObject dSpaceObject = subscription.getDSpaceObject(); if (dSpaceObject.getType() == COMMUNITY) { - communities.addAll(contentUpdates.get(Community.class.getSimpleName()) + communities.addAll(contentUpdates.get(Community.class.getSimpleName().toLowerCase()) .findUpdates(context, dSpaceObject, frequency)); } else if (dSpaceObject.getType() == COLLECTION) { - collections.addAll(contentUpdates.get(Collection.class.getSimpleName()) + collections.addAll(contentUpdates.get(Collection.class.getSimpleName().toLowerCase()) .findUpdates(context, dSpaceObject, frequency)); + } else { + log.warn("found an invalid DSpace Object type ({}) among subscriptions to send", + dSpaceObject.getType()); + continue; } var ePerson = subscription.getEPerson(); From a26790fe4b0059c59b4d99a1e21fa189d6b1d606 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 3 Feb 2023 15:10:46 +0100 Subject: [PATCH 230/394] [CST-7756] removed Items section --- dspace/config/emails/subscriptions_content | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dspace/config/emails/subscriptions_content b/dspace/config/emails/subscriptions_content index 76d682aaa1b0..f4d557e1f778 100644 --- a/dspace/config/emails/subscriptions_content +++ b/dspace/config/emails/subscriptions_content @@ -2,7 +2,6 @@ ## ## Parameters: {0} Collections updates ## {1} Communities updates -## {2} Items updates This email is sent from DSpace based on the chosen subscription preferences. @@ -14,6 +13,3 @@ Collections ----------- List of changed items : ${params[1]} -Items ------ -List of changed items : ${params[2]} From 9fe1953d07d0bc69baa1043e6f3c2bd4e8dea678 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Fri, 3 Feb 2023 17:22:26 +0100 Subject: [PATCH 231/394] [CST-7756] added missing IT --- .../dspace/content/CommunityServiceImpl.java | 2 + .../dspace/eperson/SubscribeServiceImpl.java | 8 +- .../SubscriptionRestRepository.java | 9 +- .../rest/SubscriptionRestRepositoryIT.java | 229 ++++++++++++++++++ 4 files changed, 239 insertions(+), 9 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 624fe74fe05e..923745f761bb 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -551,6 +551,8 @@ protected void rawDelete(Context context, Community community) context.addEvent(new Event(Event.DELETE, Constants.COMMUNITY, community.getID(), community.getHandle(), getIdentifiers(context, community))); + subscribeService.deleteByDspaceObject(context, community); + // Remove collections Iterator collections = community.getCollections().iterator(); diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java index 7f5b9740ef3e..2e4d94f4431e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeServiceImpl.java @@ -19,7 +19,6 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -51,13 +50,12 @@ public List findAll(Context context, String resourceType, Integer if (StringUtils.isBlank(resourceType)) { return subscriptionDAO.findAllOrderedByDSO(context, limit, offset); } else { - if (resourceType.equals(Item.class.getSimpleName()) || - resourceType.equals(Collection.class.getSimpleName()) || + if (resourceType.equals(Collection.class.getSimpleName()) || resourceType.equals(Community.class.getSimpleName())) { return subscriptionDAO.findAllOrderedByIDAndResourceType(context, resourceType, limit, offset); } else { - log.error("Resource type must be Item, Collection or Community"); - throw new Exception("Resource type must be Item, Collection or Community"); + log.error("Resource type must be Collection or Community"); + throw new Exception("Resource type must be Collection or Community"); } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 3cf2699d8802..c136c9ee2d26 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -21,7 +21,6 @@ import java.util.UUID; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.BadRequestException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,6 +30,7 @@ import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.SubscriptionRest; @@ -149,7 +149,7 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, String epersonId = req.getParameter("eperson_id"); String dsoId = req.getParameter("resource"); - if (Objects.isNull(dsoId) || Objects.isNull(epersonId)) { + if (StringUtils.isBlank(dsoId) || StringUtils.isBlank(epersonId)) { throw new UnprocessableEntityException("Both eperson than DSpaceObject uuids must be provieded!"); } @@ -157,7 +157,7 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); EPerson ePerson = ePersonService.findByIdOrLegacyId(context, epersonId); if (Objects.isNull(ePerson) || Objects.isNull(dSpaceObject)) { - throw new BadRequestException("Id of person or dspace object must represents reals ids"); + throw new DSpaceBadRequestException("Id of person or dspace object must represents reals ids"); } if (dSpaceObject.getType() == COMMUNITY || dSpaceObject.getType() == COLLECTION) { @@ -175,7 +175,8 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, context.commit(); return converter.toRest(subscription, utils.obtainProjection()); } else { - throw new BadRequestException("Currently subscription is supported only for Community and Collection"); + throw new DSpaceBadRequestException( + "Currently subscription is supported only for Community and Collection"); } } catch (SQLException sqlException) { throw new SQLException(sqlException.getMessage(), sqlException); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index 4a214645278e..ad742a0fc1a9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,8 +34,11 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.ItemBuilder; import org.dspace.builder.SubscribeBuilder; import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; import org.dspace.eperson.SubscriptionParameter; @@ -51,6 +55,7 @@ */ public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationTest { + private Community subCommunity; private Collection collection; @Before @@ -62,6 +67,9 @@ public void setUp() throws Exception { parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); + subCommunity = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Test Sub Community") + .build(); collection = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") .withSubmitterGroup(eperson) @@ -325,6 +333,124 @@ public void findSubscriptionsByEPersonForbiddenTest() throws Exception { .andExpect(status().isForbidden()); } + @Test + public void findByEPersonAndDsoAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList).build(); + + List subscriptionParameterList2 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter2 = new SubscriptionParameter(); + subscriptionParameter2.setName("frequency"); + subscriptionParameter2.setValue("W"); + subscriptionParameterList2.add(subscriptionParameter2); + Subscription subscription2 = SubscribeBuilder.subscribeBuilder(context, + "content", collection, eperson, subscriptionParameterList2).build(); + + List subscriptionParameterList3 = new ArrayList<>(); + SubscriptionParameter subscriptionParameter3 = new SubscriptionParameter(); + subscriptionParameter3.setName("frequency"); + subscriptionParameter3.setValue("M"); + subscriptionParameterList3.add(subscriptionParameter3); + Subscription subscription3 = SubscribeBuilder.subscribeBuilder(context, + "content", subCommunity, eperson, subscriptionParameterList3).build(); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", eperson.getID().toString()) + .param("resource", collection.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.containsInAnyOrder( + SubscriptionMatcher.matchSubscription(subscription1), + SubscriptionMatcher.matchSubscription(subscription2) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))); + + getClient(tokenAdmin).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", eperson.getID().toString()) + .param("resource", subCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.contains( + SubscriptionMatcher.matchSubscription(subscription3) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findByEPersonAndDsoOwnerTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription1 = SubscribeBuilder.subscribeBuilder(context, + "content", subCommunity, eperson, subscriptionParameterList).build(); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", eperson.getID().toString()) + .param("resource", subCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subscriptions", Matchers.contains( + SubscriptionMatcher.matchSubscription(subscription1) + ))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))); + } + + @Test + public void findByEPersonAndDsoUnauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + SubscribeBuilder.subscribeBuilder(context, "content", subCommunity, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", eperson.getID().toString()) + .param("resource", subCommunity.getID().toString())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findByEPersonAndDsoForbiddenTest() throws Exception { + context.turnOffAuthorisationSystem(); + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + SubscribeBuilder.subscribeBuilder(context, "content", subCommunity, admin, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/search/findByEPersonAndDso") + .param("eperson_id", admin.getID().toString()) + .param("resource", subCommunity.getID().toString())) + .andExpect(status().isForbidden()); + } + @Test public void createSubscriptionUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -422,6 +548,109 @@ public void createSubscriptionByEPersonTest() throws Exception { } } + @Test + public void createSubscriptionForItemByEPersonTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", item1.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createSubscriptionForItemByAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Item item1 = ItemBuilder.createItem(context, collection) + .withTitle("Public item") + .build(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("resource", item1.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createSubscriptionWrongResourceUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("resource", UUID.randomUUID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()); + } + + @Test + public void createSubscriptionMissingResourceUUIDTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Map map = new HashMap<>(); + map.put("subscriptionType", "content"); + List> list = new ArrayList<>(); + Map sub_list = new HashMap<>(); + sub_list.put("name", "frequency"); + sub_list.put("value", "W"); + list.add(sub_list); + map.put("subscriptionParameterList", list); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + } + @Test public void createSubscriptionWithWrongSubscriptionParameterNameTest() throws Exception { context.turnOffAuthorisationSystem(); From 9558330aeaabde4f3ff262e653a79e7dd64c7232 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 3 Feb 2023 18:37:55 +0100 Subject: [PATCH 232/394] [CST-7754] updated javadoc --- .../SolrServiceSupervisionOrderIndexingPlugin.java | 6 +++--- .../enumeration/SupervisionOrderType.java | 4 ++-- .../supervision/service/SupervisionOrderService.java | 12 ++++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java index 5af8b61ebbb7..116b5ec88d1b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceSupervisionOrderIndexingPlugin.java @@ -23,11 +23,11 @@ import org.springframework.beans.factory.annotation.Autowired; /** - * A Solr Indexing plugin responsible for + * A Solr Indexing plugin responsible adding a `supervised` field. * When item being indexed is a workspace or workflow item, * and at least one supervision order is defined - * a 'supervised' field with value 'true' will be added to the solr document, - * if not a 'supervised' will be 'false' + * the 'supervised' field with value 'true' will be added to the solr document, + * if no supervision orders are defined field will be set to 'false' * * @author Mohamed Eskander (mohamed.eskander at 4science dot it) */ diff --git a/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java index 53d45bd2a2df..4f6b888d6082 100644 --- a/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java +++ b/dspace-api/src/main/java/org/dspace/supervision/enumeration/SupervisionOrderType.java @@ -11,8 +11,8 @@ /** * This Enum holds a representation of all the possible supervision order types *

- * OBSERVER: grant READ permission to the item - * EDITOR: grant READ and WRITE permissions to the item + * OBSERVER: grant READ permission to the supervised item + * EDITOR: grant READ and WRITE permissions to the supervised item * NONE: no grants *

* diff --git a/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java index 6211351435b8..0a3b6dae4b9c 100644 --- a/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java +++ b/dspace-api/src/main/java/org/dspace/supervision/service/SupervisionOrderService.java @@ -25,6 +25,8 @@ public interface SupervisionOrderService extends DSpaceCRUDService { /** + * Creates a new SupervisionOrder + * * @param context The DSpace context * @param item the item * @param group the group @@ -34,6 +36,8 @@ public interface SupervisionOrderService extends DSpaceCRUDService findAll(Context context) throws SQLException; /** + * Find all supervision orders for a given Item + * * @param context The DSpace context * @param item the item * @return all Supervision Orders related to the item @@ -49,6 +55,9 @@ public interface SupervisionOrderService extends DSpaceCRUDService findByItem(Context context, Item item) throws SQLException; /** + * + * Find a supervision order depending on given Item and Group + * * @param context The DSpace context * @param item the item * @param group the group @@ -58,6 +67,9 @@ public interface SupervisionOrderService extends DSpaceCRUDService Date: Fri, 3 Feb 2023 18:38:14 +0100 Subject: [PATCH 233/394] [CST-7754] updated comment --- dspace/config/spring/api/discovery.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index daea075fd0e6..f1784a948b04 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -913,7 +913,7 @@
- + From 744748ab66f07575f258774f561a3e681ff5129a Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 3 Feb 2023 18:38:47 +0100 Subject: [PATCH 234/394] [CST-7754] withdrawn item should not allow supervision order --- .../app/rest/repository/SupervisionOrderRestRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java index e253035be8f7..fb2f589dbc2a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SupervisionOrderRestRepository.java @@ -116,7 +116,7 @@ public SupervisionOrderRest createAndReturn(Context context) throws AuthorizeExc throw new UnprocessableEntityException("Item with uuid: " + itemId + " not found"); } - if (item.isArchived()) { + if (item.isArchived() || item.isWithdrawn()) { throw new UnprocessableEntityException("An archived Item with uuid: " + itemId + " can't be supervised"); } From a1550f3e5c08c6f9e8851fced970f0bfa8f758ea Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 3 Feb 2023 18:41:00 +0100 Subject: [PATCH 235/394] [CST-7754] moved supervision orders test on workflow items to SupervisionOrderRestRepositoryIT, added test covering the scenario when an admin tries to set a supervision order on a withdrawn item, and when an admin sets a supervision order on a workflow item before sending it back to workspace: supervisor can edit the item only when it goes to workspace status. --- .../SupervisionOrderRestRepositoryIT.java | 419 +++++++++++++----- .../rest/WorkflowItemRestRepositoryIT.java | 141 ------ 2 files changed, 316 insertions(+), 244 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java index 96e0f9ac4de8..bccfddda0acf 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -22,10 +22,12 @@ import java.io.InputStream; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.ws.rs.core.MediaType; +import org.dspace.app.rest.matcher.WorkflowItemMatcher; import org.dspace.app.rest.matcher.WorkspaceItemMatcher; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.repository.SupervisionOrderRestRepository; @@ -36,6 +38,7 @@ import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.SupervisionOrderBuilder; +import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.Item; @@ -45,8 +48,10 @@ import org.dspace.eperson.Group; import org.dspace.supervision.SupervisionOrder; import org.dspace.supervision.service.SupervisionOrderService; +import org.dspace.workflow.WorkflowItem; import org.hamcrest.Matchers; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -63,6 +68,15 @@ public class SupervisionOrderRestRepositoryIT extends AbstractControllerIntegrat @Autowired private InstallItemService installItemService; + @Before + public void init() throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + context.restoreAuthSystemState(); + } + @Test public void findAllByAnonymousUserTest() throws Exception { getClient().perform(get("/api/core/supervisionorders/")) @@ -83,10 +97,6 @@ public void findAllByAdminTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -135,10 +145,6 @@ public void findOneByAnonymousUserTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -170,10 +176,6 @@ public void findOneByNotAdminUserTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -207,10 +209,6 @@ public void findOneByAdminTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -240,55 +238,6 @@ public void findOneByAdminTest() throws Exception { .andExpect(jsonPath("$", matchSuperVisionOrder(supervisionOrder))); } - @Test - public void findOneByAdminAndItemIsWithdrawnTest() throws Exception { - - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - Collection col1 = - CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1") - .build(); - - Item item = - ItemBuilder.createItem(context, col1) - .withTitle("item title") - .build(); - - Group group = - GroupBuilder.createGroup(context) - .withName("group") - .addMember(eperson) - .build(); - - SupervisionOrder supervisionOrder = - SupervisionOrderBuilder.createSupervisionOrder(context, item, group) - .build(); - - context.restoreAuthSystemState(); - - String patchBody = getPatchContent(List.of(new ReplaceOperation("/withdrawn", true))); - - String adminToken = getAuthToken(admin.getEmail(), password); - // withdraw item - getClient(adminToken).perform(patch("/api/core/items/" + item.getID()) - .content(patchBody) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.uuid", Matchers.is(item.getID().toString()))) - .andExpect(jsonPath("$.withdrawn", Matchers.is(true))) - .andExpect(jsonPath("$.inArchive", Matchers.is(false))); - - getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrder.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", matchSuperVisionOrder(context.reloadEntity(supervisionOrder)))); - - } - @Test public void findOneByAdminButNotFoundTest() throws Exception { int fakeId = 12354326; @@ -302,10 +251,6 @@ public void findByItemByAnonymousUserTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -336,10 +281,6 @@ public void findByItemByNotAdminUserTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -579,10 +520,6 @@ public void createByAdminButIncorrectTypeParameterTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -618,10 +555,6 @@ public void createByAdminButNotFoundItemOrGroupTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -888,10 +821,6 @@ public void deleteByAnonymousUserTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -923,10 +852,6 @@ public void deleteByNotAdminUserTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -968,10 +893,6 @@ public void deleteByAdminTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -1021,10 +942,6 @@ public void deleteItemThenSupervisionOrderBeDeletedTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -1065,10 +982,6 @@ public void deleteGroupThenSupervisionOrderBeDeletedTest() throws Exception { context.turnOffAuthorisationSystem(); - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection 1") @@ -1105,7 +1018,7 @@ public void deleteGroupThenSupervisionOrderBeDeletedTest() throws Exception { } @Test - public void deleteWorkspaceItemThenSupervisionOrderBeDeletedTest() throws Exception { + public void deleteWorkspaceItemThenSupervisionOrderIsDeletedTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = @@ -1159,7 +1072,7 @@ public void deleteWorkspaceItemThenSupervisionOrderBeDeletedTest() throws Except } @Test - public void installWorkspaceItemThenSupervisionOrderBeDeletedTest() throws Exception { + public void installWorkspaceItemThenSupervisionOrderIsDeletedTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = @@ -1243,4 +1156,304 @@ public void installWorkspaceItemThenSupervisionOrderBeDeletedTest() throws Excep } + @Test + public void createOnArchivedAndWithdrawnItemsNotAllowedTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection publications = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Publications") + .withEntityType("Publication") + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test1@email.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + Group groupB = + GroupBuilder.createGroup(context) + .withName("group B") + .addMember(eperson) + .build(); + + Item item = ItemBuilder.createItem(context, publications) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid",item.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + + // withdraw the item, supervision order creation still not possible + + String patchBody = getPatchContent(List.of(new ReplaceOperation("/withdrawn", true))); + + getClient(adminToken).perform(patch("/api/core/items/" + item.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid",item.getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", item.getID().toString()) + .param("group", groupB.getID().toString()) + .param("type", "OBSERVER") + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void createSupervisionOnWorkspaceThenSubmitToWorkflowTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + EPerson reviewer = + EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password) + .build(); + + Collection collection = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withWorkflowGroup("reviewer", reviewer) + .build(); + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + + InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); + + WorkspaceItem witem = + WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item 1") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .withFulltext("simple-article.pdf", "/local/path/simple-article.pdf", pdf) + .grantLicense() + .build(); + + context.restoreAuthSystemState(); + + // create a supervision order on workspaceItem to groupA + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", witem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + String authTokenA = getAuthToken(userA.getEmail(), password); + + // a simple patch to update an existent metadata + String patchBody = + getPatchContent(List.of( + new ReplaceOperation("/sections/traditionalpageone/dc.title/0", Map.of("value", "New Title")) + )); + + // supervisor update the title of the workspaceItem + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", + // check the new title and untouched values + Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "New Title", "2017-10-17", "ExtraEntry" + )))); + + // supervisor check that title has been updated + getClient(authTokenA).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$", Matchers.is( + WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem, "New Title", "2017-10-17", "ExtraEntry" + )))); + + AtomicReference idRef = new AtomicReference(); + try { + String adminToken = getAuthToken(admin.getEmail(), password); + String reviewerToken = getAuthToken(reviewer.getEmail(), password); + + // submit the workspaceitem to start the workflow + getClient(adminToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + witem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()) + .andDo(result -> + idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + // supervisor can read the workflowitem + getClient(authTokenA) + .perform(get("/api/workflow/workflowitems/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "New Title", "2017-10-17")))) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))); + + // reviewer can read the workflowitem + getClient(reviewerToken) + .perform(get("/api/workflow/workflowitems/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$",Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( + null, "New Title", "2017-10-17")))) + .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))); + + // a simple patch to update an existent metadata + String patchBodyTwo = + getPatchContent(List.of(new ReplaceOperation("/metadata/dc.title", Map.of("value", "edited title")))); + + // supervisor can't edit item in workflow + getClient(authTokenA).perform(patch("/api/core/items/" + witem.getItem().getID()) + .content(patchBodyTwo) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + // supervisor can't edit the workflow item + getClient(authTokenA).perform(patch("/api/workflow/workflowitems/" + idRef.get()) + .content(patchBody) + .contentType(contentType)) + .andExpect(status().isUnprocessableEntity()); + + } finally { + if (idRef.get() != null) { + WorkflowItemBuilder.deleteWorkflowItem(idRef.get()); + } + } + } + + @Test + public void supervisionOrderAddedToWorkflowItemThenSentBackToWorkspace() throws Exception { + + context.turnOffAuthorisationSystem(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Publication") + .withWorkflowGroup("reviewer", admin).build(); + + WorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, collection) + .withSubmitter(admin) + .withTitle("this is the title") + .withIssueDate("1982-12-17") + .grantLicense().build(); + + + EPerson userA = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("userA@test.com") + .withPassword(password) + .build(); + + Group groupA = + GroupBuilder.createGroup(context) + .withName("group A") + .addMember(userA) + .build(); + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/supervisionorders/") + .param("uuid", workflowItem.getItem().getID().toString()) + .param("group", groupA.getID().toString()) + .param("type", "EDITOR") + .contentType(contentType)) + .andExpect(status().isCreated()); + + String workflowItemPatchBody = + getPatchContent(List.of(new ReplaceOperation("/metadata/dc.title", Map.of("value", "edited title")))); + + String authTokenA = getAuthToken(userA.getEmail(), password); + // supervisor can't edit item in workflow + getClient(authTokenA).perform(patch("/api/core/items/" + workflowItem.getItem().getID()) + .content(workflowItemPatchBody) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + AtomicReference idRef = new AtomicReference<>(); + + try { + // Delete the workflowitem + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(delete("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().is(204)); + + getClient(adminToken).perform(get("/api/submission/workspaceitems/search/findBySubmitter") + .param("uuid", admin.getID().toString())) + .andExpect(status().isOk()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$._embedded.workspaceitems[0].id"))); + + String workspaceItemPatchBody = getPatchContent(List.of( + new ReplaceOperation("/sections/traditionalpageone/dc.title/0", Map.of("value", "New Title")) + )); + + getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + idRef.get()) + .content(workspaceItemPatchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + } finally { + Integer id = idRef.get(); + if (Objects.nonNull(id)) { + WorkspaceItemBuilder.deleteWorkspaceItem(id); + } + } + } } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index ac743b2e5a09..c43821d4a013 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -46,7 +46,6 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; -import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; @@ -56,7 +55,6 @@ import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; import org.dspace.eperson.EPerson; -import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.state.Step; @@ -2124,143 +2122,4 @@ public void whenWorkspaceitemBecomeWorkflowitemWithAccessConditionsTheItemMustBe WorkflowItemBuilder.deleteWorkflowItem(idRef.get()); } } - - @Test - public void createSupervisionOnWorkspaceThenSubmitToBeWorkflowTest() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = - CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - EPerson reviewer = - EPersonBuilder.createEPerson(context) - .withEmail("reviewer1@example.com") - .withPassword(password) - .build(); - - Collection collection = - CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1") - .withWorkflowGroup("reviewer", reviewer) - .build(); - - EPerson userA = - EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withEmail("userA@test.com") - .withPassword(password) - .build(); - - Group groupA = - GroupBuilder.createGroup(context) - .withName("group A") - .addMember(userA) - .build(); - - InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); - - WorkspaceItem witem = - WorkspaceItemBuilder.createWorkspaceItem(context, collection) - .withTitle("Workspace Item 1") - .withIssueDate("2017-10-17") - .withSubject("ExtraEntry") - .withFulltext("simple-article.pdf", "/local/path/simple-article.pdf", pdf) - .grantLicense() - .build(); - - context.restoreAuthSystemState(); - - // create a supervision order on workspaceItem to groupA - getClient(getAuthToken(admin.getEmail(), password)) - .perform(post("/api/core/supervisionorders/") - .param("uuid", witem.getItem().getID().toString()) - .param("group", groupA.getID().toString()) - .param("type", "EDITOR") - .contentType(contentType)) - .andExpect(status().isCreated()); - - String authTokenA = getAuthToken(userA.getEmail(), password); - - // a simple patch to update an existent metadata - String patchBody = - getPatchContent(List.of( - new ReplaceOperation("/sections/traditionalpageone/dc.title/0", Map.of("value", "New Title")) - )); - - // supervisor update the title of the workspaceItem - getClient(authTokenA).perform(patch("/api/submission/workspaceitems/" + witem.getID()) - .content(patchBody) - .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.errors").doesNotExist()) - .andExpect(jsonPath("$", - // check the new title and untouched values - Matchers.is( - WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem, "New Title", "2017-10-17", "ExtraEntry" - )))); - - // supervisor check that title has been updated - getClient(authTokenA).perform(get("/api/submission/workspaceitems/" + witem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.errors").doesNotExist()) - .andExpect(jsonPath("$", Matchers.is( - WorkspaceItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem, "New Title", "2017-10-17", "ExtraEntry" - )))); - - AtomicReference idRef = new AtomicReference(); - try { - String adminToken = getAuthToken(admin.getEmail(), password); - String reviewerToken = getAuthToken(reviewer.getEmail(), password); - - // submit the workspaceitem to start the workflow - getClient(adminToken).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") - .content("/api/submission/workspaceitems/" + witem.getID()) - .contentType(textUriContentType)) - .andExpect(status().isCreated()) - .andDo(result -> - idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - - // supervisor can read the workflowitem - getClient(authTokenA) - .perform(get("/api/workflow/workflowitems/" + idRef.get())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$",Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( - null, "New Title", "2017-10-17")))) - .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))); - - // reviewer can read the workflowitem - getClient(reviewerToken) - .perform(get("/api/workflow/workflowitems/" + idRef.get())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$",Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssued( - null, "New Title", "2017-10-17")))) - .andExpect(jsonPath("$.sections.defaultAC.discoverable", is(true))); - - // a simple patch to update an existent metadata - String patchBodyTwo = - getPatchContent(List.of(new ReplaceOperation("/metadata/dc.title", Map.of("value", "edited title")))); - - // supervisor can't edit item in workflow - getClient(authTokenA).perform(patch("/api/core/items/" + witem.getItem().getID()) - .content(patchBodyTwo) - .contentType(contentType)) - .andExpect(status().isForbidden()); - - // supervisor can't edit the workflow item - getClient(authTokenA).perform(patch("/api/workflow/workflowitems/" + idRef.get()) - .content(patchBody) - .contentType(contentType)) - .andExpect(status().isUnprocessableEntity()); - - } finally { - if (idRef.get() != null) { - WorkflowItemBuilder.deleteWorkflowItem(idRef.get()); - } - } - } - } From 3fe4fd192fedfd51baf69092325f862c3e08f530 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 16 Nov 2022 16:47:56 +1300 Subject: [PATCH 236/394] [TLC-380] Browse links and "by regex" links in item display --- .../java/org/dspace/browse/CrossLinks.java | 64 ++++++++++- .../test/data/dspaceFolder/config/local.cfg | 5 +- .../repository/BrowseLinkRestRepository.java | 102 ++++++++++++++++++ .../app/rest/BrowseLinkRestRepositoryIT.java | 86 +++++++++++++++ 4 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java diff --git a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java index aa30862e3c34..d4818c6fb19f 100644 --- a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java +++ b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java @@ -59,7 +59,32 @@ public CrossLinks() * @return true/false */ public boolean hasLink(String metadata) { - return links.containsKey(metadata); + // Resolve wildcards properly, eg. dc.subject.other matches a configuration for dc.subject.* + for (String key : links.keySet()) { + if (null != key && key.endsWith(".*")) { + // A substring of length-1, also substracting the wildcard should work as a "startsWith" + // check for the field eg. dc.subject.* -> dc.subject is the start of dc.subject.other + if (null != metadata && metadata.startsWith(key.substring(0, key.length()-1 - ".*".length()))) { + return true; + } + } else { + // Exact match, if the key field has no .* wildcard + if (links.containsKey(metadata)) { + return true; + } + } + } + + return false; + } + + /** + * Is there a link for the given browse name (eg 'author') + * @param browseIndexName + * @return true/false + */ + public boolean hasBrowseName(String browseIndexName) { + return links.containsValue(browseIndexName); } /** @@ -69,6 +94,41 @@ public boolean hasLink(String metadata) { * @return type */ public String getLinkType(String metadata) { - return links.get(metadata); + return findLinkType(metadata); + } + + /** + * Get full map of field->indexname link configurations + * @return + */ + public Map getLinks() { + return links; + } + + /** + * Find and return the browse name for a given metadata field. + * If the link key contains a wildcard eg dc.subject.*, it should + * match dc.subject.other, etc. + * @param metadata + * @return + */ + public String findLinkType(String metadata) { + // Resolve wildcards properly, eg. dc.subject.other matches a configuration for dc.subject.* + for (String key : links.keySet()) { + if (null != key && key.endsWith(".*")) { + // A substring of length-1, also substracting the wildcard should work as a "startsWith" + // check for the field eg. dc.subject.* -> dc.subject is the start of dc.subject.other + if (null != metadata && metadata.startsWith(key.substring(0, key.length()-1 - ".*".length()))) { + return links.get(key); + } + } else { + // Exact match, if the key field has no .* wildcard + if (links.containsKey(metadata)) { + return links.get(key); + } + } + } + // No match + return null; } } diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 9cc6b7ebea92..6305b25d5620 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -78,7 +78,7 @@ handle.remote-resolver.enabled = true # Whether to enable the DSpace listhandles resolver that lists all available # handles for this DSpace installation. # Defaults to "false" which means is possible to obtain the list of handles -# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`. +# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`. handle.hide.listhandles = false ##################### @@ -171,3 +171,6 @@ choices.plugin.dspace.object.owner = EPersonAuthority choices.presentation.dspace.object.owner = suggest authority.controlled.dspace.object.owner = true +# Configuration required for thorough testing of browse links +webui.browse.link.1 = author:dc.contributor.* +webui.browse.link.2 = subject:dc.subject.* \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java new file mode 100644 index 000000000000..98d9f427e953 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java @@ -0,0 +1,102 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.dspace.app.rest.model.BrowseIndexRest; +import org.dspace.browse.BrowseException; +import org.dspace.browse.BrowseIndex; +import org.dspace.browse.CrossLinks; +import org.dspace.core.Context; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible for listing link behaviour for various browse definitions and fields. + * Note "link" here refers to rendering an HTML link from a displayed metadata value to a browse page + * not a HAL _link + * + * @author Kim Shepherd + */ +@Component(BrowseIndexRest.CATEGORY + ".browselink") +public class BrowseLinkRestRepository extends DSpaceRestRepository { + + /** + * Return a browse definition for a given metadata field name if it is configured + * as a browse link, or null (404) + * + * @param context + * the dspace context + * @param metadataField + * the rest object id + * @return + */ + @Override + @PreAuthorize("permitAll()") + public BrowseIndexRest findOne(Context context, String metadataField) { + BrowseIndexRest bi = null; + BrowseIndex bix = null; + try { + CrossLinks cl = new CrossLinks(); + if (cl.hasLink(metadataField)) { + // Get the index name for this + String browseIndexName = cl.getLinkType(metadataField); + bix = BrowseIndex.getBrowseIndex(browseIndexName); + } + } catch (BrowseException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (bix != null) { + bi = converter.toRest(bix, utils.obtainProjection()); + } + return bi; + } + + /** + * Get paginated list of all browse index definitions for configured browse links + * + * @param context + * the dspace context + * @param pageable + * object embedding the requested pagination info + * @return + */ + @Override + public Page findAll(Context context, Pageable pageable) { + try { + CrossLinks cl = new CrossLinks(); + List linkedIndexes = new ArrayList<>(); + Map links = cl.getLinks(); + for (String field : links.keySet()) { + if (cl.hasLink(field)) { + String indexName = cl.getLinkType(field); + if (indexName != null) { + BrowseIndex bix = BrowseIndex.getBrowseIndex(indexName); + if (bix != null) { + linkedIndexes.add(bix); + } + } + } + } + return converter.toRestPage(linkedIndexes, pageable, linkedIndexes.size(), utils.obtainProjection()); + } catch (BrowseException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public Class getDomainClass() { + return BrowseIndexRest.class; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java new file mode 100644 index 000000000000..8d5bcc120692 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java @@ -0,0 +1,86 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.dspace.app.rest.matcher.BrowseIndexMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.authority.service.MetadataAuthorityService; +import org.dspace.services.ConfigurationService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test to test the /api/discover/browselinks endpoint + * + * @author Kim Shepherd + */ +public class BrowseLinkRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + ConfigurationService configurationService; + + @Autowired + MetadataAuthorityService metadataAuthorityService; + + /** + * Expect a single author browse definition + * @throws Exception + */ + @Test + public void findOne() throws Exception { + //When we call the root endpoint + getClient().perform(get("/api/discover/browselinks/dc.contributor.author")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + //The array of browse index should have a size 1 + .andExpect(jsonPath("$.id", is("author"))) + + //Check that all (and only) the default browse indexes are present + .andExpect(jsonPath("$.metadataBrowse", is(true))) + ; + } + + /** + * Expect a list of browse definitions that are also configured for link rendering + * @throws Exception + */ + @Test + public void findAll() throws Exception { + //When we call the root endpoint + getClient().perform(get("/api/discover/browselinks")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + // Expect TWO results, author and browse (see dspace-api test data local.cfg_ + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + + //The array of browse index should have a size 2 + .andExpect(jsonPath("$._embedded.browses", hasSize(2))) + + //Check that all (and only) the default browse indexes are present + .andExpect(jsonPath("$._embedded.browses", containsInAnyOrder( + BrowseIndexMatcher.contributorBrowseIndex("asc"), + BrowseIndexMatcher.subjectBrowseIndex("asc") + ))) + ; + } + + + +} From 14b5f0e343775f6db90f9a424fd9fb48c1304819 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 22 Nov 2022 16:15:40 +1300 Subject: [PATCH 237/394] [TLC-380] Fix checkstyle and duplicate code --- .../java/org/dspace/browse/CrossLinks.java | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java index d4818c6fb19f..1ce2e558866d 100644 --- a/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java +++ b/dspace-api/src/main/java/org/dspace/browse/CrossLinks.java @@ -59,23 +59,7 @@ public CrossLinks() * @return true/false */ public boolean hasLink(String metadata) { - // Resolve wildcards properly, eg. dc.subject.other matches a configuration for dc.subject.* - for (String key : links.keySet()) { - if (null != key && key.endsWith(".*")) { - // A substring of length-1, also substracting the wildcard should work as a "startsWith" - // check for the field eg. dc.subject.* -> dc.subject is the start of dc.subject.other - if (null != metadata && metadata.startsWith(key.substring(0, key.length()-1 - ".*".length()))) { - return true; - } - } else { - // Exact match, if the key field has no .* wildcard - if (links.containsKey(metadata)) { - return true; - } - } - } - - return false; + return findLinkType(metadata) != null; } /** @@ -118,7 +102,7 @@ public String findLinkType(String metadata) { if (null != key && key.endsWith(".*")) { // A substring of length-1, also substracting the wildcard should work as a "startsWith" // check for the field eg. dc.subject.* -> dc.subject is the start of dc.subject.other - if (null != metadata && metadata.startsWith(key.substring(0, key.length()-1 - ".*".length()))) { + if (null != metadata && metadata.startsWith(key.substring(0, key.length() - 1 - ".*".length()))) { return links.get(key); } } else { From 9f67c6a9f427c54fa8f322f3d8dce8ad49a37037 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 22 Nov 2022 16:32:35 +1300 Subject: [PATCH 238/394] [TLC-380] Further checkstyle fixes --- .../app/rest/repository/BrowseLinkRestRepository.java | 1 - .../org/dspace/app/rest/BrowseLinkRestRepositoryIT.java | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java index 98d9f427e953..0e50c28307fb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.repository; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java index 8d5bcc120692..319d55f36bf7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java @@ -7,9 +7,13 @@ */ package org.dspace.app.rest; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.matcher.BrowseIndexMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; From 135385816a873538854df25a7113908810c1a022 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 22 Nov 2022 16:47:28 +1300 Subject: [PATCH 239/394] [TLC-380] Further checkstyle fixes --- .../java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java index 319d55f36bf7..69f2ece10604 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java @@ -10,8 +10,8 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; From d85de892617c3cc63e56655dca20937516ca86df Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Jan 2023 15:36:48 +1300 Subject: [PATCH 240/394] [TLC-380] Refactor browses endpoint for browse link usage --- .../repository/BrowseIndexRestRepository.java | 65 +++++++++++ .../repository/BrowseLinkRestRepository.java | 101 ------------------ .../app/rest/BrowseLinkRestRepositoryIT.java | 90 ---------------- .../app/rest/BrowsesResourceControllerIT.java | 53 ++++++++- 4 files changed, 117 insertions(+), 192 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java delete mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java index 01277ff29b19..e7262f3d28e7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java @@ -7,12 +7,18 @@ */ package org.dspace.app.rest.repository; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.model.BrowseIndexRest; import org.dspace.browse.BrowseException; import org.dspace.browse.BrowseIndex; +import org.dspace.browse.CrossLinks; import org.dspace.core.Context; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -53,6 +59,65 @@ public Page findAll(Context context, Pageable pageable) { } } + /** + * Find a browse index by a specific field + * @param field + * @return + * @throws SQLException + */ + @SearchRestMethod(name = "byField") + public BrowseIndexRest findByField(@Parameter(value = "field", required = true) String field) + throws SQLException { + BrowseIndexRest bi = null; + BrowseIndex bix = null; + try { + CrossLinks cl = new CrossLinks(); + if (cl.hasLink(field)) { + // Get the index name for this + String browseIndexName = cl.getLinkType(field); + bix = BrowseIndex.getBrowseIndex(browseIndexName); + } + } catch (BrowseException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (bix != null) { + bi = converter.toRest(bix, utils.obtainProjection()); + } + return bi; + } + + /** + * Get paginated list of all browse index definitions for configured browse links + * + * @param context + * the dspace context + * @param pageable + * object embedding the requested pagination info + * @return + */ + @SearchRestMethod(name = "allLinked") + public Page findAllLinked(Context context, Pageable pageable) { + try { + CrossLinks cl = new CrossLinks(); + List linkedIndexes = new ArrayList<>(); + Map links = cl.getLinks(); + for (String field : links.keySet()) { + if (cl.hasLink(field)) { + String indexName = cl.getLinkType(field); + if (indexName != null) { + BrowseIndex bix = BrowseIndex.getBrowseIndex(indexName); + if (bix != null) { + linkedIndexes.add(bix); + } + } + } + } + return converter.toRestPage(linkedIndexes, pageable, linkedIndexes.size(), utils.obtainProjection()); + } catch (BrowseException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override public Class getDomainClass() { return BrowseIndexRest.class; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java deleted file mode 100644 index 0e50c28307fb..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseLinkRestRepository.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.repository; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.dspace.app.rest.model.BrowseIndexRest; -import org.dspace.browse.BrowseException; -import org.dspace.browse.BrowseIndex; -import org.dspace.browse.CrossLinks; -import org.dspace.core.Context; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.stereotype.Component; - -/** - * This is the repository responsible for listing link behaviour for various browse definitions and fields. - * Note "link" here refers to rendering an HTML link from a displayed metadata value to a browse page - * not a HAL _link - * - * @author Kim Shepherd - */ -@Component(BrowseIndexRest.CATEGORY + ".browselink") -public class BrowseLinkRestRepository extends DSpaceRestRepository { - - /** - * Return a browse definition for a given metadata field name if it is configured - * as a browse link, or null (404) - * - * @param context - * the dspace context - * @param metadataField - * the rest object id - * @return - */ - @Override - @PreAuthorize("permitAll()") - public BrowseIndexRest findOne(Context context, String metadataField) { - BrowseIndexRest bi = null; - BrowseIndex bix = null; - try { - CrossLinks cl = new CrossLinks(); - if (cl.hasLink(metadataField)) { - // Get the index name for this - String browseIndexName = cl.getLinkType(metadataField); - bix = BrowseIndex.getBrowseIndex(browseIndexName); - } - } catch (BrowseException e) { - throw new RuntimeException(e.getMessage(), e); - } - if (bix != null) { - bi = converter.toRest(bix, utils.obtainProjection()); - } - return bi; - } - - /** - * Get paginated list of all browse index definitions for configured browse links - * - * @param context - * the dspace context - * @param pageable - * object embedding the requested pagination info - * @return - */ - @Override - public Page findAll(Context context, Pageable pageable) { - try { - CrossLinks cl = new CrossLinks(); - List linkedIndexes = new ArrayList<>(); - Map links = cl.getLinks(); - for (String field : links.keySet()) { - if (cl.hasLink(field)) { - String indexName = cl.getLinkType(field); - if (indexName != null) { - BrowseIndex bix = BrowseIndex.getBrowseIndex(indexName); - if (bix != null) { - linkedIndexes.add(bix); - } - } - } - } - return converter.toRestPage(linkedIndexes, pageable, linkedIndexes.size(), utils.obtainProjection()); - } catch (BrowseException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - - @Override - public Class getDomainClass() { - return BrowseIndexRest.class; - } -} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java deleted file mode 100644 index 69f2ece10604..000000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowseLinkRestRepositoryIT.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest; - -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasSize; -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; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.dspace.app.rest.matcher.BrowseIndexMatcher; -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.content.authority.service.MetadataAuthorityService; -import org.dspace.services.ConfigurationService; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * Integration test to test the /api/discover/browselinks endpoint - * - * @author Kim Shepherd - */ -public class BrowseLinkRestRepositoryIT extends AbstractControllerIntegrationTest { - @Autowired - ConfigurationService configurationService; - - @Autowired - MetadataAuthorityService metadataAuthorityService; - - /** - * Expect a single author browse definition - * @throws Exception - */ - @Test - public void findOne() throws Exception { - //When we call the root endpoint - getClient().perform(get("/api/discover/browselinks/dc.contributor.author")) - //The status has to be 200 OK - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - - //The array of browse index should have a size 1 - .andExpect(jsonPath("$.id", is("author"))) - - //Check that all (and only) the default browse indexes are present - .andExpect(jsonPath("$.metadataBrowse", is(true))) - ; - } - - /** - * Expect a list of browse definitions that are also configured for link rendering - * @throws Exception - */ - @Test - public void findAll() throws Exception { - //When we call the root endpoint - getClient().perform(get("/api/discover/browselinks")) - //The status has to be 200 OK - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - - // Expect TWO results, author and browse (see dspace-api test data local.cfg_ - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", is(2))) - .andExpect(jsonPath("$.page.totalPages", is(1))) - .andExpect(jsonPath("$.page.number", is(0))) - - //The array of browse index should have a size 2 - .andExpect(jsonPath("$._embedded.browses", hasSize(2))) - - //Check that all (and only) the default browse indexes are present - .andExpect(jsonPath("$._embedded.browses", containsInAnyOrder( - BrowseIndexMatcher.contributorBrowseIndex("asc"), - BrowseIndexMatcher.subjectBrowseIndex("asc") - ))) - ; - } - - - -} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index 3768ce19aacc..4980f1345a33 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -980,7 +980,7 @@ public void findBrowseByTitleItemsWithScope() throws Exception { /** * This test was introduced to reproduce the bug DS-4269 Pagination links must be consistent also when there is not * explicit pagination parameters in the request (i.e. defaults apply) - * + * * @throws Exception */ public void browsePaginationWithoutExplicitParams() throws Exception { @@ -2125,4 +2125,55 @@ public void testBrowseByDateIssuedItemsFullProjectionTest() throws Exception { .andExpect(jsonPath("$._embedded.items[0]._embedded.owningCollection._embedded.adminGroup", nullValue())); } + + /** + * Expect a single author browse definition + * @throws Exception + */ + @Test + public void findOneLinked() throws Exception { + //When we call the root endpoint + getClient().perform(get("/api/discover/browses/search/byField?field=dc.contributor.author")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + //The array of browse index should have a size 1 + .andExpect(jsonPath("$.id", is("author"))) + + //Check that all (and only) the default browse indexes are present + .andExpect(jsonPath("$.metadataBrowse", is(true))) + ; + } + + /** + * Expect a list of browse definitions that are also configured for link rendering + * @throws Exception + */ + @Test + public void findAllLinked() throws Exception { + //When we call the root endpoint + getClient().perform(get("/api/discover/browses/search/allLinked")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + // Expect TWO results, author and browse (see dspace-api test data local.cfg_ + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + + //The array of browse index should have a size 2 + .andExpect(jsonPath("$._embedded.browses", hasSize(2))) + + //Check that all (and only) the default browse indexes are present + .andExpect(jsonPath("$._embedded.browses", containsInAnyOrder( + BrowseIndexMatcher.contributorBrowseIndex("asc"), + BrowseIndexMatcher.subjectBrowseIndex("asc") + ))) + ; + } } From 1c2a1538fd4eb1aa031c309d2460e7f6a7b657dd Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 26 Jan 2023 14:47:02 +1300 Subject: [PATCH 241/394] [TLC-249] Improve 'findByFields' browse refactor --- .../repository/BrowseIndexRestRepository.java | 31 +++++++++++++++++++ .../app/rest/BrowsesResourceControllerIT.java | 16 ++++++++++ 2 files changed, 47 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java index e7262f3d28e7..b16787e88c34 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java @@ -86,6 +86,37 @@ public BrowseIndexRest findByField(@Parameter(value = "field", required = true) return bi; } + /** + * Find a browse index by a list of fields (first match will be returned) + * @param fields + * @return + * @throws SQLException + */ + @SearchRestMethod(name = "byFields") + public BrowseIndexRest findByFields(@Parameter(value = "fields", required = true) String[] fields) + throws SQLException { + BrowseIndexRest bi = null; + BrowseIndex bix = null; + try { + // Find the browse index definition that matches any field - once found, return + for (String field : fields) { + CrossLinks cl = new CrossLinks(); + if (cl.hasLink(field)) { + // Get the index name for this + String browseIndexName = cl.getLinkType(field); + bix = BrowseIndex.getBrowseIndex(browseIndexName); + break; + } + } + } catch (BrowseException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (bix != null) { + bi = converter.toRest(bix, utils.obtainProjection()); + } + return bi; + } + /** * Get paginated list of all browse index definitions for configured browse links * diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index 4980f1345a33..1fd3e10bf736 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -2147,6 +2147,22 @@ public void findOneLinked() throws Exception { ; } + @Test + public void findTwoLinked() throws Exception { + //When we call the root endpoint + getClient().perform(get("/api/discover/browses/search/byFields?fields=dc.contributor.author,dc.date.issued")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + // The array of browse index should have a size 2 and contain both configured indices + .andExpect(jsonPath("$._embedded.browses", hasSize(2))) + .andExpect(jsonPath("$._embedded.browses", containsInAnyOrder( + BrowseIndexMatcher.contributorBrowseIndex("asc"), + BrowseIndexMatcher.subjectBrowseIndex("asc") + ))); + } + /** * Expect a list of browse definitions that are also configured for link rendering * @throws Exception From 18609c0a208e366f241052482c513ccffe536dc5 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 26 Jan 2023 15:35:45 +1300 Subject: [PATCH 242/394] [TLC-380] Fix browse resource byFields IT --- .../app/rest/BrowsesResourceControllerIT.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index 1fd3e10bf736..69ceda59a19f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -2148,19 +2148,22 @@ public void findOneLinked() throws Exception { } @Test - public void findTwoLinked() throws Exception { + public void findOneLinkedPassingTwoFields() throws Exception { //When we call the root endpoint - getClient().perform(get("/api/discover/browses/search/byFields?fields=dc.contributor.author,dc.date.issued")) - //The status has to be 200 OK + getClient().perform(get("/api/discover/browses/search/byFields") + .param("fields", "dc.contributor.author") + .param("fields", "dc.date.issued")) + // The status has to be 200 OK .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" + // We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - // The array of browse index should have a size 2 and contain both configured indices - .andExpect(jsonPath("$._embedded.browses", hasSize(2))) - .andExpect(jsonPath("$._embedded.browses", containsInAnyOrder( - BrowseIndexMatcher.contributorBrowseIndex("asc"), - BrowseIndexMatcher.subjectBrowseIndex("asc") - ))); + // The array of browse index should have a size 1 because 'author' was the first + // field we passed, and we only expect the first index returned + // (this method is used by field components to find out if / how to link a value to a browse + // index, and this could mean multiple metadata fields) + .andExpect(jsonPath("$.id", is("author"))) + // Check that all (and only) the default browse indexes are present + .andExpect(jsonPath("$.metadataBrowse", is(true))); } /** From 56ed4aadd445b4a822fde9f51a3d48685e96be74 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 6 Feb 2023 14:54:00 +1300 Subject: [PATCH 243/394] [TLC-380] Remove unused methods, add ITs --- .../repository/BrowseIndexRestRepository.java | 61 ------------------ .../app/rest/BrowsesResourceControllerIT.java | 63 +++++++------------ dspace/config/dspace.cfg | 8 +-- 3 files changed, 26 insertions(+), 106 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java index b16787e88c34..8ffefb619b47 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java @@ -8,10 +8,8 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; @@ -59,33 +57,6 @@ public Page findAll(Context context, Pageable pageable) { } } - /** - * Find a browse index by a specific field - * @param field - * @return - * @throws SQLException - */ - @SearchRestMethod(name = "byField") - public BrowseIndexRest findByField(@Parameter(value = "field", required = true) String field) - throws SQLException { - BrowseIndexRest bi = null; - BrowseIndex bix = null; - try { - CrossLinks cl = new CrossLinks(); - if (cl.hasLink(field)) { - // Get the index name for this - String browseIndexName = cl.getLinkType(field); - bix = BrowseIndex.getBrowseIndex(browseIndexName); - } - } catch (BrowseException e) { - throw new RuntimeException(e.getMessage(), e); - } - if (bix != null) { - bi = converter.toRest(bix, utils.obtainProjection()); - } - return bi; - } - /** * Find a browse index by a list of fields (first match will be returned) * @param fields @@ -117,38 +88,6 @@ public BrowseIndexRest findByFields(@Parameter(value = "fields", required = true return bi; } - /** - * Get paginated list of all browse index definitions for configured browse links - * - * @param context - * the dspace context - * @param pageable - * object embedding the requested pagination info - * @return - */ - @SearchRestMethod(name = "allLinked") - public Page findAllLinked(Context context, Pageable pageable) { - try { - CrossLinks cl = new CrossLinks(); - List linkedIndexes = new ArrayList<>(); - Map links = cl.getLinks(); - for (String field : links.keySet()) { - if (cl.hasLink(field)) { - String indexName = cl.getLinkType(field); - if (indexName != null) { - BrowseIndex bix = BrowseIndex.getBrowseIndex(indexName); - if (bix != null) { - linkedIndexes.add(bix); - } - } - } - } - return converter.toRestPage(linkedIndexes, pageable, linkedIndexes.size(), utils.obtainProjection()); - } catch (BrowseException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - @Override public Class getDomainClass() { return BrowseIndexRest.class; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index 69ceda59a19f..24850cd11b13 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -2132,24 +2132,23 @@ public void testBrowseByDateIssuedItemsFullProjectionTest() throws Exception { */ @Test public void findOneLinked() throws Exception { - //When we call the root endpoint - getClient().perform(get("/api/discover/browses/search/byField?field=dc.contributor.author")) - //The status has to be 200 OK + // When we call the search endpoint + getClient().perform(get("/api/discover/browses/search/byFields") + .param("fields", "dc.contributor.author")) + // The status has to be 200 OK .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" + // We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - - //The array of browse index should have a size 1 + // The browse definition ID should be "author" .andExpect(jsonPath("$.id", is("author"))) - - //Check that all (and only) the default browse indexes are present + // It should be configured as a metadata browse .andExpect(jsonPath("$.metadataBrowse", is(true))) ; } @Test public void findOneLinkedPassingTwoFields() throws Exception { - //When we call the root endpoint + // When we call the search endpoint getClient().perform(get("/api/discover/browses/search/byFields") .param("fields", "dc.contributor.author") .param("fields", "dc.date.issued")) @@ -2157,42 +2156,26 @@ public void findOneLinkedPassingTwoFields() throws Exception { .andExpect(status().isOk()) // We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - // The array of browse index should have a size 1 because 'author' was the first - // field we passed, and we only expect the first index returned - // (this method is used by field components to find out if / how to link a value to a browse - // index, and this could mean multiple metadata fields) + // The browse definition ID should be "author" .andExpect(jsonPath("$.id", is("author"))) - // Check that all (and only) the default browse indexes are present + // It should be configured as a metadata browse .andExpect(jsonPath("$.metadataBrowse", is(true))); } - /** - * Expect a list of browse definitions that are also configured for link rendering - * @throws Exception - */ @Test - public void findAllLinked() throws Exception { - //When we call the root endpoint - getClient().perform(get("/api/discover/browses/search/allLinked")) - //The status has to be 200 OK - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - - // Expect TWO results, author and browse (see dspace-api test data local.cfg_ - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", is(2))) - .andExpect(jsonPath("$.page.totalPages", is(1))) - .andExpect(jsonPath("$.page.number", is(0))) - - //The array of browse index should have a size 2 - .andExpect(jsonPath("$._embedded.browses", hasSize(2))) + public void findUnconfiguredFields() throws Exception { + // When we call the search endpoint with a field that isn't configured for any browse links + getClient().perform(get("/api/discover/browses/search/byFields") + .param("fields", "dc.identifier.uri")) + // The status has to be 204 NO CONTENT + .andExpect(status().isNoContent()); + } - //Check that all (and only) the default browse indexes are present - .andExpect(jsonPath("$._embedded.browses", containsInAnyOrder( - BrowseIndexMatcher.contributorBrowseIndex("asc"), - BrowseIndexMatcher.subjectBrowseIndex("asc") - ))) - ; + @Test + public void findBrowseLinksWithMissingParameter() throws Exception { + // When we call the search endpoint with a field that isn't configured for any browse links + getClient().perform(get("/api/discover/browses/search/byFields")) + // The status has to be 400 BAD REQUEST + .andExpect(status().isBadRequest()); } } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 404fd6b53003..129ad0ca5646 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -338,7 +338,7 @@ handle.dir = ${dspace.dir}/handle-server # Whether to enable the DSpace listhandles resolver that lists all available # handles for this DSpace installation. # Defaults to "false" which means is possible to obtain the list of handles -# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`. +# of this DSpace installation, whenever the `handle.remote-resolver.enabled = true`. # handle.hide.listhandles = false ##### Authorization system configuration - Delegate ADMIN ##### @@ -1228,8 +1228,6 @@ plugin.named.org.dspace.sort.OrderFormatDelegate= \ # starting with the value of the link clicked on. # # The default below defines the authors to link to other publications by that author -# -# TODO: UNSUPPORTED in DSpace 7.0 webui.browse.link.1 = author:dc.contributor.* #### Display browse frequencies @@ -1539,10 +1537,10 @@ log.report.dir = ${dspace.dir}/log # The below example will run this task daily, every 5 minutes # google.analytics.cron = 0 0/5 * * * ? -# Defines a Measurement Protocol API Secret to be used to track interactions which occur outside of the user's browser. +# Defines a Measurement Protocol API Secret to be used to track interactions which occur outside of the user's browser. # For example , this is required to track downloads of bitstreams. This setting is only used by Google Analytics 4. # For more details see https://developers.google.com/analytics/devguides/collection/protocol/ga4 -# google.analytics.api-secret = +# google.analytics.api-secret = #################################################################### #---------------------------------------------------------------# From 450956de8606096ca11a4fbec5054a33638a125c Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 3 Feb 2023 16:44:58 +0100 Subject: [PATCH 244/394] #8629: Make sure temporary worfklow groups of reviewers get deleted --- .../state/actions/userassignment/AutoAssignAction.java | 4 ++++ .../src/test/data/dspaceFolder/config/spring/api/workflow.xml | 1 + dspace/config/spring/api/workflow.xml | 1 + 3 files changed, 6 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java index 6a5c850f40bb..21b11c8012e5 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java @@ -80,6 +80,10 @@ public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl } //Delete our workflow item role since the users have been assigned workflowItemRoleService.delete(c, workflowItemRole); + if (role.isDeleteTemporaryGroup()) { + // Delete temporary groups created after members have workflow task assigned + groupService.delete(c, workflowItemRole.getGroup()); + } } } else { log.warn(LogHelper.getHeader(c, "Error while executing auto assign action", diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml index 6e987ae8b0f6..a83be3fa339b 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/workflow.xml @@ -153,6 +153,7 @@ + diff --git a/dspace/config/spring/api/workflow.xml b/dspace/config/spring/api/workflow.xml index 004ff1d75752..448e10fd4682 100644 --- a/dspace/config/spring/api/workflow.xml +++ b/dspace/config/spring/api/workflow.xml @@ -151,6 +151,7 @@ + From 07bc6d3141c9ef7667a7254b654e499eeedac44b Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 3 Feb 2023 16:46:03 +0100 Subject: [PATCH 245/394] #8629: PR feedback refactor --- .../java/org/dspace/xmlworkflow/Role.java | 16 ++ .../state/actions/ActionAdvancedInfo.java | 42 ++--- .../AcceptEditRejectAction.java | 2 - .../processingaction/ReviewAction.java | 22 +-- .../processingaction/ScoreReviewAction.java | 34 ++-- ...ava => ScoreReviewActionAdvancedInfo.java} | 47 +----- .../SelectReviewerAction.java | 159 ++++++++++++------ .../SelectReviewerActionAdvancedInfo.java | 34 +--- .../SingleUserReviewAction.java | 51 +++--- .../userassignment/AutoAssignAction.java | 2 +- .../xmlworkflow/XmlWorkflowServiceIT.java | 3 + ...tingReviewActionAdvancedInfoConverter.java | 37 ---- ...coreReviewActionAdvancedInfoConverter.java | 36 ++++ ...ctReviewerActionAdvancedInfoConverter.java | 1 - .../app/rest/model/AdvancedInfoRest.java | 37 ++++ .../RatingReviewActionAdvancedInfoRest.java | 94 ----------- .../ScoreReviewActionAdvancedInfoRest.java | 35 ++++ .../SelectReviewerActionAdvancedInfoRest.java | 56 +----- .../app/rest/model/WorkflowActionRest.java | 35 ---- .../app/rest/TaskRestRepositoriesIT.java | 3 +- .../rest/WorkflowActionRestRepositoryIT.java | 24 +-- .../rest/matcher/WorkflowActionMatcher.java | 16 +- dspace/config/dspace.cfg | 1 - dspace/config/modules/actions.cfg | 4 - dspace/config/modules/workflow.cfg | 7 +- 25 files changed, 345 insertions(+), 453 deletions(-) rename dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/{RatingReviewActionAdvancedInfo.java => ScoreReviewActionAdvancedInfo.java} (52%) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AdvancedInfoRest.java delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java delete mode 100644 dspace/config/modules/actions.cfg diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/Role.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/Role.java index bfc5654cdd20..5b5ba5c1d3ba 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/Role.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/Role.java @@ -41,6 +41,9 @@ public class Role implements BeanNameAware { @Autowired private WorkflowItemRoleService workflowItemRoleService; + // Whether or not to delete temporary group made attached to the WorkflowItemRole for this role in AutoAssignAction + private boolean deleteTemporaryGroup = false; + private String id; private String name; private String description; @@ -153,4 +156,17 @@ public void setScope(Scope scope) { public void setInternal(boolean internal) { isInternal = internal; } + + public boolean isDeleteTemporaryGroup() { + return deleteTemporaryGroup; + } + + /** + * Setter for config that indicated whether or not to delete temporary group made attached to the + * WorkflowItemRole for this role in AutoAssignAction + * @param deleteTemporaryGroup + */ + public void setDeleteTemporaryGroup(boolean deleteTemporaryGroup) { + this.deleteTemporaryGroup = deleteTemporaryGroup; + } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java index 5a6451807e20..b49fdb34f869 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/ActionAdvancedInfo.java @@ -7,27 +7,29 @@ */ package org.dspace.xmlworkflow.state.actions; -public interface ActionAdvancedInfo { - /** - * Getter for the type of Action - * - * @return String of format "action_info_" + Action type - */ - String getType(); +/** + * Interface for the shared properties of an 'advancedInfo' section of an advanced workflow {@link Action} + * Implementations of this class will define the specific fields per action that will need to be defined/configured + * to pass along this info to REST endpoint + */ +public abstract class ActionAdvancedInfo { - /** - * Setter for the Action type to be set in format "action_info_" + type - * - * @param type The type to be set - */ - void setType(String type); + protected String type; + protected String id; - /** - * Getter for the id of Action - * - * @return MD5 hash String of this Action - */ - String getId(); + protected final static String TYPE_PREFIX = "action_info_"; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = TYPE_PREFIX + type; + } + + public String getId() { + return id; + } /** * Setter for the Action id to be set. @@ -35,6 +37,6 @@ public interface ActionAdvancedInfo { * * @param type The type of this Action to be included in the MD5 hash */ - void setId(String type); + protected abstract void generateId(String type); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java index 7adbd9b6b21e..67b400c6592e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java @@ -32,10 +32,8 @@ */ public class AcceptEditRejectAction extends ProcessingAction { - private static final String SUBMIT_APPROVE = "submit_approve"; private static final String SUBMITTER_IS_DELETED_PAGE = "submitter_deleted"; - //TODO: rename to AcceptAndEditMetadataAction @Override diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java index 0e44f4b428bc..bd74ab3c7152 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java @@ -34,10 +34,8 @@ public class ReviewAction extends ProcessingAction { public static final int MAIN_PAGE = 0; public static final int REJECT_PAGE = 1; - private static final String SUBMIT_APPROVE = "submit_approve"; private static final String SUBMITTER_IS_DELETED_PAGE = "submitter_deleted"; - @Override public void activate(Context c, XmlWorkflowItem wfItem) { @@ -51,7 +49,7 @@ public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl case SUBMIT_APPROVE: return processAccept(c, wfi); case SUBMIT_REJECT: - return processRejectPage(c, wfi, step, request); + return super.processRejectPage(c, wfi, request); case SUBMITTER_IS_DELETED_PAGE: return processSubmitterIsDeletedPage(c, wfi, request); default: @@ -75,24 +73,6 @@ public ActionResult processAccept(Context c, XmlWorkflowItem wfi) throws SQLExce return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } - public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException, IOException { - String reason = request.getParameter("reason"); - if (reason == null || 0 == reason.trim().length()) { - request.setAttribute("page", REJECT_PAGE); - addErrorField(request, "reason"); - return new ActionResult(ActionResult.TYPE.TYPE_ERROR); - } - - //We have pressed reject, so remove the task the user has & put it back to a workspace item - XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService() - .sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), - this.getProvenanceStartId(), reason); - - - return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); - } - public ActionResult processSubmitterIsDeletedPage(Context c, XmlWorkflowItem wfi, HttpServletRequest request) throws SQLException, AuthorizeException, IOException { if (request.getParameter("submit_delete") != null) { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java index 35916094693a..43a3decacc7e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewAction.java @@ -8,8 +8,8 @@ package org.dspace.xmlworkflow.state.actions.processingaction; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -60,13 +60,9 @@ public void activate(Context c, XmlWorkflowItem wf) { @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) throws SQLException, AuthorizeException { - if (super.isOptionInParam(request)) { - switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) { - case SUBMIT_SCORE: - return processSetRating(c, wfi, request); - default: - return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); - } + if (super.isOptionInParam(request) && + StringUtils.equalsIgnoreCase(Util.getSubmitButton(request, SUBMIT_CANCEL), SUBMIT_SCORE)) { + return processSetRating(c, wfi, request); } return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } @@ -103,14 +99,14 @@ private ActionResult processSetRating(Context c, XmlWorkflowItem wfi, HttpServle */ private boolean checkRequestValid(int score, String review) { if (score > this.maxValue) { - log.error(String.format("%s only allows max rating %s (config workflow-actions.xml), given rating of " + - "%s not allowed.", this.getClass().toString(), this.maxValue, score)); + log.error("{} only allows max rating {} (config workflow-actions.xml), given rating of " + + "{} not allowed.", this.getClass().toString(), this.maxValue, score); return false; } if (StringUtils.isBlank(review) && this.descriptionRequired) { - log.error(String.format("%s has config descriptionRequired=%s (workflow-actions.xml), so rating " + + log.error("{} has config descriptionRequired=true (workflow-actions.xml), so rating " + "requests without 'review' query param containing description are not allowed", - this.getClass().toString(), this.descriptionRequired)); + this.getClass().toString()); return false; } return true; @@ -128,14 +124,12 @@ protected List getAdvancedOptions() { @Override protected List getAdvancedInfo() { - List advancedInfo = new ArrayList<>(); - RatingReviewActionAdvancedInfo ratingReviewActionAdvancedInfo = new RatingReviewActionAdvancedInfo(); - ratingReviewActionAdvancedInfo.setDescriptionRequired(descriptionRequired); - ratingReviewActionAdvancedInfo.setMaxValue(maxValue); - ratingReviewActionAdvancedInfo.setType(SUBMIT_SCORE); - ratingReviewActionAdvancedInfo.setId(SUBMIT_SCORE); - advancedInfo.add(ratingReviewActionAdvancedInfo); - return advancedInfo; + ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo = new ScoreReviewActionAdvancedInfo(); + scoreReviewActionAdvancedInfo.setDescriptionRequired(descriptionRequired); + scoreReviewActionAdvancedInfo.setMaxValue(maxValue); + scoreReviewActionAdvancedInfo.setType(SUBMIT_SCORE); + scoreReviewActionAdvancedInfo.generateId(SUBMIT_SCORE); + return Collections.singletonList(scoreReviewActionAdvancedInfo); } /** diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java similarity index 52% rename from dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java rename to dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java index ffaaf3d54412..5b97fe3195ae 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/RatingReviewActionAdvancedInfo.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreReviewActionAdvancedInfo.java @@ -12,71 +12,34 @@ /** * Class that holds the advanced information needed for the - * {@link org.dspace.xmlworkflow.state.actions.processingaction.RatingReviewAction} + * {@link org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewAction} + * See config {@code workflow-actions.cfg} */ -public class RatingReviewActionAdvancedInfo implements ActionAdvancedInfo { +public class ScoreReviewActionAdvancedInfo extends ActionAdvancedInfo { private boolean descriptionRequired; private int maxValue; - private String type; - private String id; - - /** - * Generic getter for the descriptionRequired boolean - * - * @return the descriptionRequired boolean value - */ public boolean isDescriptionRequired() { return descriptionRequired; } - /** - * Generic setter for the descriptionRequired boolean - * - * @param descriptionRequired The descriptionRequired boolean to be set - */ public void setDescriptionRequired(boolean descriptionRequired) { this.descriptionRequired = descriptionRequired; } - /** - * Generic getter for the maxValue - * - * @return the maxValue value - */ public int getMaxValue() { return maxValue; } - /** - * Generic setter for the maxValue - * - * @param maxValue The maxValue to be set - */ public void setMaxValue(int maxValue) { this.maxValue = maxValue; } @Override - public String getType() { - return type; - } - - @Override - public void setType(String type) { - this.type = "action_info_" + type; - } - - @Override - public String getId() { - return id; - } - - @Override - public void setId(String type) { + public void generateId(String type) { String idString = type + ";descriptionRequired," + descriptionRequired + ";maxValue," + maxValue; - this.id = DigestUtils.md5DigestAsHex(idString.getBytes()); + super.id = DigestUtils.md5DigestAsHex(idString.getBytes()); } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java index f0fe3cbb98ca..0e8ab40a5205 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerAction.java @@ -12,9 +12,13 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; +import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; @@ -43,13 +47,13 @@ */ public class SelectReviewerAction extends ProcessingAction { - public static final int SEARCH_RESULTS_PAGE = 1; - - public static final int RESULTS_PER_PAGE = 5; + private static final Logger log = LogManager.getLogger(SelectReviewerAction.class); private static final String SUBMIT_CANCEL = "submit_cancel"; - private static final String SUBMIT_SEARCH = "submit_search"; private static final String SUBMIT_SELECT_REVIEWER = "submit_select_reviewer"; + private static final String PARAM_REVIEWER = "eperson"; + + private static final String CONFIG_REVIEWER_GROUP = "action.selectrevieweraction.group"; private Role role; @@ -65,6 +69,9 @@ public class SelectReviewerAction extends ProcessingAction { @Autowired private GroupService groupService; + private static Group selectFromReviewsGroup; + private static boolean selectFromReviewsGroupInitialised = false; + @Override public void activate(Context c, XmlWorkflowItem wf) { @@ -72,7 +79,7 @@ public void activate(Context c, XmlWorkflowItem wf) { @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) - throws SQLException, AuthorizeException { + throws SQLException, AuthorizeException { String submitButton = Util.getSubmitButton(request, SUBMIT_CANCEL); //Check if our user has pressed cancel @@ -80,7 +87,7 @@ public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl //Send us back to the submissions page return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } else if (submitButton.startsWith(SUBMIT_SELECT_REVIEWER)) { - return processSelectReviewers(c, wfi, step, request); + return processSelectReviewers(c, wfi, request); } //There are only 2 active buttons on this page, so if anything else happens just return an error @@ -88,57 +95,84 @@ public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl } /** - * Method to handle the "submit_select_reviewer" action + * Method to handle the {@link this#SUBMIT_SELECT_REVIEWER} action: + * - will retrieve the reviewer(s) uuid from request (param {@link this#PARAM_REVIEWER}) + * - assign them to a {@link WorkflowItemRole} + * - In {@link org.dspace.xmlworkflow.state.actions.userassignment.AutoAssignAction} these reviewer(s) will get + * claimed task for this {@link XmlWorkflowItem} + * Will result in error if: + * - No reviewer(s) uuid in request (param {@link this#PARAM_REVIEWER}) + * - If none of the reviewer(s) uuid passed along result in valid EPerson + * - If the reviewer(s) passed along are not in {@link this#selectFromReviewsGroup} when it is set * * @param c current DSpace session * @param wfi the item on which the action is to be performed - * @param step the workflow step in which the action is performed * @param request the current client request * @return the result of performing the action - * @throws SQLException - * @throws AuthorizeException */ - public ActionResult processSelectReviewers(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) + private ActionResult processSelectReviewers(Context c, XmlWorkflowItem wfi, HttpServletRequest request) throws SQLException, AuthorizeException { //Retrieve the identifier of the eperson which will do the reviewing - String[] reviewerIds = request.getParameterValues("eperson"); + String[] reviewerIds = request.getParameterValues(PARAM_REVIEWER); if (ArrayUtils.isEmpty(reviewerIds)) { return new ActionResult(ActionResult.TYPE.TYPE_ERROR); } List reviewers = new ArrayList<>(); for (String reviewerId : reviewerIds) { - reviewers.add(ePersonService.find(c, UUID.fromString(reviewerId))); + EPerson reviewer = ePersonService.find(c, UUID.fromString(reviewerId)); + if (reviewer == null) { + log.warn("No EPerson found with uuid {}", reviewerId); + } else { + reviewers.add(reviewer); + } } - if (reviewers.size() == 1) { - if (!groupService.allMembers(c, getGroup()).contains(reviewers.get(0))) { - return new ActionResult(ActionResult.TYPE.TYPE_ERROR); + + if (!this.checkReviewersValid(c, reviewers)) { + return new ActionResult(ActionResult.TYPE.TYPE_ERROR); + } + + createWorkflowItemRole(c, wfi, reviewers); + return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); + } + + private boolean checkReviewersValid(Context c, List reviewers) throws SQLException { + if (reviewers.size() == 0) { + return false; + } + Group group = this.getGroup(c); + if (group != null) { + for (EPerson reviewer: reviewers) { + if (!groupService.isMember(c, reviewer, group)) { + log.error("Reviewers selected must be member of group {}", group.getID()); + return false; + } } - //We have a reviewer, assign him, the workflowitemrole will be translated into a task in the autoassign - WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c); + } + return true; + } + + private WorkflowItemRole createWorkflowItemRole(Context c, XmlWorkflowItem wfi, List reviewers) + throws SQLException, AuthorizeException { + WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c); + workflowItemRole.setRoleId(getRole().getId()); + workflowItemRole.setWorkflowItem(wfi); + if (reviewers.size() == 1) { + // 1 reviewer in workflowitemrole => will be translated into a claimed task in the autoassign workflowItemRole.setEPerson(reviewers.get(0)); - workflowItemRole.setRoleId(getRole().getId()); - workflowItemRole.setWorkflowItem(wfi); - workflowItemRoleService.update(c, workflowItemRole); - return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } else { - if (!groupService.allMembers(c, getGroup()).containsAll(reviewers)) { - return new ActionResult(ActionResult.TYPE.TYPE_ERROR); - } - //We have multiple reviewers, create a group and assign this group, the workflowitemrole will be - // translated into a task in the autoassign + // multiple reviewers, create a temporary group and assign this group, the workflowitemrole will be + // translated into a claimed task for reviewers in the autoassign, where group will be deleted c.turnOffAuthorisationSystem(); - Group reviewerGroup = groupService.create(c); + Group selectedReviewsGroup = groupService.create(c); + groupService.setName(selectedReviewsGroup, "selectedReviewsGroup_" + wfi.getID()); for (EPerson reviewer : reviewers) { - groupService.addMember(c, reviewerGroup, reviewer); + groupService.addMember(c, selectedReviewsGroup, reviewer); } + workflowItemRole.setGroup(selectedReviewsGroup); c.restoreAuthSystemState(); - WorkflowItemRole workflowItemRole = workflowItemRoleService.create(c); - workflowItemRole.setGroup(reviewerGroup); - workflowItemRole.setRoleId(getRole().getId()); - workflowItemRole.setWorkflowItem(wfi); - workflowItemRoleService.update(c, workflowItemRole); - return new ActionResult(ActionResult.TYPE.TYPE_OUTCOME, ActionResult.OUTCOME_COMPLETE); } + workflowItemRoleService.update(c, workflowItemRole); + return workflowItemRole; } @Override @@ -158,11 +192,11 @@ protected List getAdvancedOptions() { protected List getAdvancedInfo() { List advancedInfo = new ArrayList<>(); SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo(); - if (getGroup() != null) { - selectReviewerActionAdvancedInfo.setGroup(getGroup().getID().toString()); + if (getGroup(null) != null) { + selectReviewerActionAdvancedInfo.setGroup(getGroup(null).getID().toString()); } selectReviewerActionAdvancedInfo.setType(SUBMIT_SELECT_REVIEWER); - selectReviewerActionAdvancedInfo.setId(SUBMIT_SELECT_REVIEWER); + selectReviewerActionAdvancedInfo.generateId(SUBMIT_SELECT_REVIEWER); advancedInfo.add(selectReviewerActionAdvancedInfo); return advancedInfo; } @@ -179,24 +213,45 @@ public void setRole(Role role) { /** * Get the Reviewer group from the "action.selectrevieweraction.group" property in actions.cfg by its UUID or name * Returns null if no (valid) group configured + * * @return configured reviewers Group from property or null if none */ - private Group getGroup() { - Context context = new Context(); - String groupIdOrName = configurationService.getProperty("action.selectrevieweraction.group"); - Group group = null; - - try { - // try to get group by name - group = groupService.findByName(context, groupIdOrName); - if (group == null) { - // try to get group by uuid if not a name - group = groupService.find(context, UUID.fromString(groupIdOrName)); + private Group getGroup(@Nullable Context context) { + if (selectFromReviewsGroupInitialised) { + return this.selectFromReviewsGroup; + } + if (context == null) { + context = new Context(); + } + String groupIdOrName = configurationService.getProperty(CONFIG_REVIEWER_GROUP); + + if (StringUtils.isNotBlank(groupIdOrName)) { + Group group = null; + try { + // try to get group by name + group = groupService.findByName(context, groupIdOrName); + if (group == null) { + // try to get group by uuid if not a name + group = groupService.find(context, UUID.fromString(groupIdOrName)); + } + } catch (Exception e) { + // There is an issue with the reviewer group that is set; if it is not set then can be chosen + // from all epeople + log.error("Issue with determining matching group for config {}={} for reviewer group of " + + "select reviewers workflow", CONFIG_REVIEWER_GROUP, groupIdOrName); } - } catch (Exception ignored) { - // ignore, there is no reviewer group set + + this.selectFromReviewsGroup = group; } + selectFromReviewsGroupInitialised = true; + return this.selectFromReviewsGroup; + } - return group; + /** + * To be used by IT, e.g. {@code XmlWorkflowServiceIT}, when defining new 'Reviewers' group + */ + static public void resetGroup() { + selectFromReviewsGroup = null; + selectFromReviewsGroupInitialised = false; } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java index c0b121f19d72..7a86a0b03d1f 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SelectReviewerActionAdvancedInfo.java @@ -13,50 +13,24 @@ /** * Class that holds the advanced information needed for the * {@link org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction} + * See config {@code workflow-actions.cfg} */ -public class SelectReviewerActionAdvancedInfo implements ActionAdvancedInfo { +public class SelectReviewerActionAdvancedInfo extends ActionAdvancedInfo { private String group; - private String type; - private String id; - /** - * Generic getter for the group - * - * @return the group value - */ public String getGroup() { return group; } - /** - * Generic setter for the group - * - * @param group The group to be set - */ public void setGroup(String group) { this.group = group; } @Override - public String getType() { - return type; - } - - @Override - public void setType(String type) { - this.type = "action_info_" + type; - } - - @Override - public String getId() { - return id; - } - - @Override - public void setId(String type) { + public void generateId(String type) { String idString = type + ";group," + group; - this.id = DigestUtils.md5DigestAsHex(idString.getBytes()); + super.id = DigestUtils.md5DigestAsHex(idString.getBytes()); } } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java index 9a21a1d11427..ec42b408070b 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java @@ -13,6 +13,8 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.content.WorkspaceItem; @@ -27,8 +29,8 @@ import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; /** - * Processing class of an action where a single user has - * been assigned and he can either accept/reject the workflow item + * Processing class of an action where one or more users have + * been assigned and they can either accept/reject the workflow item * or reject the task * * @author Bram De Schouwer (bram.deschouwer at dot com) @@ -37,6 +39,7 @@ * @author Mark Diggory (markd at atmire dot com) */ public class SingleUserReviewAction extends ProcessingAction { + private static final Logger log = LogManager.getLogger(SingleUserReviewAction.class); public static final int OUTCOME_REJECT = 1; @@ -50,24 +53,34 @@ public void activate(Context c, XmlWorkflowItem wfItem) { @Override public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServletRequest request) throws SQLException, AuthorizeException, IOException, WorkflowException { - if (super.isOptionInParam(request)) { - switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) { - case SUBMIT_APPROVE: - return processAccept(c, wfi); - case SUBMIT_REJECT: - if (wfi.getSubmitter() == null) { - // If the original submitter is no longer there, delete the task - return processDelete(c, wfi); - } else { - return super.processRejectPage(c, wfi, request); - } - case SUBMIT_DECLINE_TASK: - return processDecline(c, wfi); - default: - return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); - } + if (!super.isOptionInParam(request)) { + return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); + } + switch (Util.getSubmitButton(request, SUBMIT_CANCEL)) { + case SUBMIT_APPROVE: + return processAccept(c, wfi); + case SUBMIT_REJECT: + return processReject(c, wfi, request); + case SUBMIT_DECLINE_TASK: + return processDecline(c, wfi); + default: + return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); + } + } + + /** + * Process {@link super#SUBMIT_REJECT} on this action, will either: + * - If submitter of item no longer exists => Permanently delete corresponding item (no wfi/wsi remaining) + * - Otherwise: reject item back to submission => becomes wsi of submitter again + */ + private ActionResult processReject(Context c, XmlWorkflowItem wfi, HttpServletRequest request) + throws SQLException, IOException, AuthorizeException { + if (wfi.getSubmitter() == null) { + // If the original submitter is no longer there, delete the task + return processDelete(c, wfi); + } else { + return super.processRejectPage(c, wfi, request); } - return new ActionResult(ActionResult.TYPE.TYPE_CANCEL); } /** diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java index 21b11c8012e5..401a7c506b98 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/userassignment/AutoAssignAction.java @@ -80,7 +80,7 @@ public ActionResult execute(Context c, XmlWorkflowItem wfi, Step step, HttpServl } //Delete our workflow item role since the users have been assigned workflowItemRoleService.delete(c, workflowItemRole); - if (role.isDeleteTemporaryGroup()) { + if (role.isDeleteTemporaryGroup() && workflowItemRole.getGroup() != null) { // Delete temporary groups created after members have workflow task assigned groupService.delete(c, workflowItemRole.getGroup()); } diff --git a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java index 6883c5aeb9b4..865abaca2152 100644 --- a/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/xmlworkflow/XmlWorkflowServiceIT.java @@ -38,6 +38,7 @@ import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.service.XmlWorkflowService; import org.dspace.xmlworkflow.state.Workflow; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.junit.After; import org.junit.Test; @@ -131,6 +132,7 @@ public void workflowUserSingleSelectedReviewer_ItemShouldBeEditable() throws Exc ClaimedTask task = ClaimedTaskBuilder.createClaimedTask(context, colWithWorkflow, reviewManager) .withTitle("Test workflow item to reject").build(); // Set reviewer group property and add reviewer to group + SelectReviewerAction.resetGroup(); configurationService.setProperty("action.selectrevieweraction.group", "Reviewers"); Group reviewerGroup = GroupBuilder.createGroup(context).withName("Reviewers").build(); EPerson reviewer = EPersonBuilder.createEPerson(context).withEmail("reviewer@example.org").build(); @@ -172,6 +174,7 @@ public void workflowUserMultipleSelectedReviewer_ItemShouldBeEditable() throws E ClaimedTask task = ClaimedTaskBuilder.createClaimedTask(context, colWithWorkflow, reviewManager) .withTitle("Test workflow item to reject").build(); // Set reviewer group property and add reviewer to group + SelectReviewerAction.resetGroup(); configurationService.setProperty("action.selectrevieweraction.group", "Reviewers"); Group reviewerGroup = GroupBuilder.createGroup(context).withName("Reviewers").build(); EPerson reviewer1 = EPersonBuilder.createEPerson(context).withEmail("reviewer1@example.org").build(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java deleted file mode 100644 index 49edcc116d65..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RatingReviewActionAdvancedInfoConverter.java +++ /dev/null @@ -1,37 +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.converter; - -import org.dspace.app.rest.model.RatingReviewActionAdvancedInfoRest; -import org.dspace.app.rest.projection.Projection; -import org.dspace.xmlworkflow.state.actions.processingaction.RatingReviewActionAdvancedInfo; - -/** - * This converter is responsible for transforming the model representation of a RatingReviewActionAdvancedInfo to - * the REST representation of a RatingReviewActionAdvancedInfo - */ -public class RatingReviewActionAdvancedInfoConverter - implements DSpaceConverter { - - @Override - public RatingReviewActionAdvancedInfoRest convert(RatingReviewActionAdvancedInfo modelObject, - Projection projection) { - RatingReviewActionAdvancedInfoRest restModel = new RatingReviewActionAdvancedInfoRest(); - restModel.setProjection(projection); - restModel.setDescriptionRequired(modelObject.isDescriptionRequired()); - restModel.setMaxValue(modelObject.getMaxValue()); - restModel.setType(modelObject.getType()); - restModel.setId(modelObject.getId()); - return restModel; - } - - @Override - public Class getModelClass() { - return RatingReviewActionAdvancedInfo.class; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java new file mode 100644 index 000000000000..44800f6e5035 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScoreReviewActionAdvancedInfoConverter.java @@ -0,0 +1,36 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import org.dspace.app.rest.model.ScoreReviewActionAdvancedInfoRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo; + +/** + * This converter is responsible for transforming the model representation of a ScoreReviewActionAdvancedInfo to + * the REST representation of a ScoreReviewActionAdvancedInfo + */ +public class ScoreReviewActionAdvancedInfoConverter + implements DSpaceConverter { + + @Override + public ScoreReviewActionAdvancedInfoRest convert(ScoreReviewActionAdvancedInfo modelObject, + Projection projection) { + ScoreReviewActionAdvancedInfoRest restModel = new ScoreReviewActionAdvancedInfoRest(); + restModel.setDescriptionRequired(modelObject.isDescriptionRequired()); + restModel.setMaxValue(modelObject.getMaxValue()); + restModel.setType(modelObject.getType()); + restModel.setId(modelObject.getId()); + return restModel; + } + + @Override + public Class getModelClass() { + return ScoreReviewActionAdvancedInfo.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java index 8d028977af20..3dd8f0b3b754 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SelectReviewerActionAdvancedInfoConverter.java @@ -22,7 +22,6 @@ public class SelectReviewerActionAdvancedInfoConverter public SelectReviewerActionAdvancedInfoRest convert(SelectReviewerActionAdvancedInfo modelObject, Projection projection) { SelectReviewerActionAdvancedInfoRest restModel = new SelectReviewerActionAdvancedInfoRest(); - restModel.setProjection(projection); restModel.setGroup(modelObject.getGroup()); restModel.setType(modelObject.getType()); restModel.setId(modelObject.getId()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AdvancedInfoRest.java new file mode 100644 index 000000000000..4fbee25c38b6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AdvancedInfoRest.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.app.rest.model; + +import org.dspace.xmlworkflow.state.actions.ActionAdvancedInfo; + +/** + * Abstract class for {@link ActionAdvancedInfo} + * + * @author Marie Verdonck (Atmire) on 03/02/23 + */ +public abstract class AdvancedInfoRest { + + String id; + String type; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java deleted file mode 100644 index 273fe805a3f5..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RatingReviewActionAdvancedInfoRest.java +++ /dev/null @@ -1,94 +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; - -/** - * The RatingReviewActionAdvancedInfo REST Resource - */ -public class RatingReviewActionAdvancedInfoRest extends WorkflowActionRest { - - private boolean descriptionRequired; - private int maxValue; - private String type; - private String id; - - /** - * Generic getter for the description required boolean - * - * @return the description required boolean value of this RatingReviewActionAdvancedInfoRest - */ - public boolean isDescriptionRequired() { - return descriptionRequired; - } - - /** - * Generic setter for the description required boolean - * - * @param descriptionRequired The description required boolean to be set on this RatingReviewActionAdvancedInfoRest - */ - public void setDescriptionRequired(boolean descriptionRequired) { - this.descriptionRequired = descriptionRequired; - } - - /** - * Generic getter for the max value - * - * @return the max value of this RatingReviewActionAdvancedInfoRest - */ - public int getMaxValue() { - return maxValue; - } - - /** - * Generic setter for the max value - * - * @param maxValue The max value to be set on this RatingReviewActionAdvancedInfoRest - */ - public void setMaxValue(int maxValue) { - this.maxValue = maxValue; - } - - /** - * Generic getter for the type - * - * @return the type of this RatingReviewActionAdvancedInfoRest - */ - @Override - public String getType() { - return type; - } - - /** - * Generic setter for the type - * - * @param type The type to be set on this RatingReviewActionAdvancedInfoRest - */ - public void setType(String type) { - this.type = type; - } - - /** - * Generic getter for the id - * - * @return the id of this RatingReviewActionAdvancedInfoRest - */ - @Override - public String getId() { - return id; - } - - /** - * Generic setter for the id - * - * @param id The id to be set on this RatingReviewActionAdvancedInfoRest - */ - @Override - public void setId(String id) { - this.id = id; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java new file mode 100644 index 000000000000..14644be151ac --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScoreReviewActionAdvancedInfoRest.java @@ -0,0 +1,35 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +/** + * The ScoreReviewActionAdvancedInfo REST Resource, + * see {@link org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo} + */ +public class ScoreReviewActionAdvancedInfoRest extends AdvancedInfoRest { + + private boolean descriptionRequired; + private int maxValue; + + public boolean isDescriptionRequired() { + return descriptionRequired; + } + + public void setDescriptionRequired(boolean descriptionRequired) { + this.descriptionRequired = descriptionRequired; + } + + public int getMaxValue() { + return maxValue; + } + + public void setMaxValue(int maxValue) { + this.maxValue = maxValue; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java index b9f118a0799d..86b2003b07ec 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SelectReviewerActionAdvancedInfoRest.java @@ -8,68 +8,18 @@ package org.dspace.app.rest.model; /** - * The SelectReviewerActionAdvancedInfoRest REST Resource + * The SelectReviewerActionAdvancedInfoRest REST Resource, + * see {@link org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo} */ -public class SelectReviewerActionAdvancedInfoRest extends WorkflowActionRest { +public class SelectReviewerActionAdvancedInfoRest extends AdvancedInfoRest { private String groupId; - private String type; - private String id; - /** - * Generic getter for the group - * - * @return the group value of this SelectReviewerActionAdvancedInfoRest - */ public String getGroup() { return groupId; } - /** - * Generic setter for the group uuid - * - * @param groupId The group uuid to be set on this SelectReviewerActionAdvancedInfoRest - */ public void setGroup(String groupId) { this.groupId = groupId; } - - /** - * Generic getter for the type - * - * @return the type of this SelectReviewerActionAdvancedInfoRest - */ - @Override - public String getType() { - return type; - } - - /** - * Generic setter for the type - * - * @param type The type to be set on this SelectReviewerActionAdvancedInfoRest - */ - public void setType(String type) { - this.type = type; - } - - /** - * Generic getter for the id - * - * @return the id of this SelectReviewerActionAdvancedInfoRest - */ - @Override - public String getId() { - return id; - } - - /** - * Generic setter for the id - * - * @param id The id to be set on this SelectReviewerActionAdvancedInfoRest - */ - @Override - public void setId(String id) { - this.id = id; - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java index 0bac2e6fad4a..07a2c36cff96 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowActionRest.java @@ -44,67 +44,32 @@ public String getType() { return NAME; } - /** - * Generic getter for the options - * - * @return the options value of this WorkflowActionRest - */ public List getOptions() { return options; } - /** - * Generic setter for the options - * - * @param options The options to be set on this WorkflowActionRest - */ public void setOptions(List options) { this.options = options; } - /** - * Generic getter for the advanced options - * - * @return the advanced options value of this WorkflowActionRest - */ @JsonInclude(JsonInclude.Include.NON_NULL) public List getAdvancedOptions() { return advancedOptions; } - /** - * Generic setter for the advanced options - * - * @param advancedOptions The advanced options to be set on this WorkflowActionRest - */ public void setAdvancedOptions(List advancedOptions) { this.advancedOptions = advancedOptions; } - /** - * Generic getter for advanced boolean - * - * @return the advanced boolean value of this WorkflowActionRest - */ public boolean getAdvanced() { return CollectionUtils.isNotEmpty(getAdvancedOptions()); } - /** - * Generic getter for advanced info - * - * @return the advanced info value of this WorkflowActionRest - */ @JsonInclude(JsonInclude.Include.NON_NULL) public List getAdvancedInfo() { return advancedInfo; } - /** - * Generic setter for advanced info - * - * @param advancedInfo The advanced info to be set on this WorkflowActionRest - */ public void setAdvancedInfo(List advancedInfo) { this.advancedInfo = advancedInfo; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java index 203278f9f8a1..cf6ee29f6971 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java @@ -57,6 +57,7 @@ import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -4484,7 +4485,7 @@ public void selectReviewerWorkflowTest() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(0))); // Test for single reviewer - + SelectReviewerAction.resetGroup(); // Select reviewer1 as a reviewer, wf step 1 getClient(reviewManager1Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) .param("submit_select_reviewer", "true") diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java index 79c49f57f929..ec963fd2f3bb 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowActionRestRepositoryIT.java @@ -19,6 +19,7 @@ import org.dspace.app.rest.model.WorkflowActionRest; import org.dspace.app.rest.repository.WorkflowActionRestRepository; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.GroupBuilder; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.GroupService; @@ -27,7 +28,8 @@ import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; -import org.dspace.xmlworkflow.state.actions.processingaction.RatingReviewActionAdvancedInfo; +import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo; +import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerAction; import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo; import org.hamcrest.Matchers; import org.junit.Test; @@ -145,12 +147,12 @@ public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() thr String nameActionWithOptions = "scorereviewaction"; WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); - // create RatingReviewActionAdvancedInfo to compare with output - RatingReviewActionAdvancedInfo ratingReviewActionAdvancedInfo = new RatingReviewActionAdvancedInfo(); - ratingReviewActionAdvancedInfo.setDescriptionRequired(true); - ratingReviewActionAdvancedInfo.setMaxValue(5); - ratingReviewActionAdvancedInfo.setType(SUBMIT_SCORE); - ratingReviewActionAdvancedInfo.setId(SUBMIT_SCORE); + // create ScoreReviewActionAdvancedInfo to compare with output + ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo = new ScoreReviewActionAdvancedInfo(); + scoreReviewActionAdvancedInfo.setDescriptionRequired(true); + scoreReviewActionAdvancedInfo.setMaxValue(5); + scoreReviewActionAdvancedInfo.setType(SUBMIT_SCORE); + scoreReviewActionAdvancedInfo.generateId(SUBMIT_SCORE); //When we call this facets endpoint getClient(token).perform(get(WORKFLOW_ACTIONS_ENDPOINT + "/" + nameActionWithOptions)) @@ -161,7 +163,7 @@ public void getWorkflowActionByName_ExistentWithOptions_ratingreviewaction() thr .andExpect(jsonPath("$.advancedOptions", not(empty()))) .andExpect(jsonPath("$.advanced", is(true))) .andExpect(jsonPath("$.advancedInfo", Matchers.contains( - WorkflowActionMatcher.matchRatingReviewActionAdvancedInfo(ratingReviewActionAdvancedInfo)))) + WorkflowActionMatcher.matchScoreReviewActionAdvancedInfo(scoreReviewActionAdvancedInfo)))) //Matches expected corresponding rest action values .andExpect(jsonPath("$", Matchers.is( WorkflowActionMatcher.matchWorkflowActionEntry(existentWorkflow) @@ -173,8 +175,9 @@ public void getWorkflowActionByName_ExistentWithOptions_selectrevieweraction() t String token = getAuthToken(eperson.getEmail(), password); String nameActionWithOptions = "selectrevieweraction"; // create reviewers group + SelectReviewerAction.resetGroup(); context.turnOffAuthorisationSystem(); - Group group = groupService.create(context); + Group group = GroupBuilder.createGroup(context).withName("ReviewersUUIDConfig").build(); configurationService.setProperty("action.selectrevieweraction.group", group.getID()); context.restoreAuthSystemState(); @@ -182,8 +185,7 @@ public void getWorkflowActionByName_ExistentWithOptions_selectrevieweraction() t SelectReviewerActionAdvancedInfo selectReviewerActionAdvancedInfo = new SelectReviewerActionAdvancedInfo(); selectReviewerActionAdvancedInfo.setGroup(group.getID().toString()); selectReviewerActionAdvancedInfo.setType("submit_select_reviewer"); - selectReviewerActionAdvancedInfo.setId("submit_select_reviewer"); - + selectReviewerActionAdvancedInfo.generateId("submit_select_reviewer"); WorkflowActionConfig existentWorkflow = xmlWorkflowFactory.getActionByName(nameActionWithOptions); //When we call this facets endpoint diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java index 781b95fd9efd..e727eb8accb9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/WorkflowActionMatcher.java @@ -14,7 +14,7 @@ import org.dspace.app.rest.model.WorkflowActionRest; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; -import org.dspace.xmlworkflow.state.actions.processingaction.RatingReviewActionAdvancedInfo; +import org.dspace.xmlworkflow.state.actions.processingaction.ScoreReviewActionAdvancedInfo; import org.dspace.xmlworkflow.state.actions.processingaction.SelectReviewerActionAdvancedInfo; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -42,15 +42,15 @@ public static Matcher matchWorkflowActionEntry(WorkflowActionCon /** * Matcher to check the contents of the advancedInfo for "ratingreviewaction" - * @param ratingReviewActionAdvancedInfo identical RatingReviewActionAdvancedInfo object + * @param scoreReviewActionAdvancedInfo identical ScoreReviewActionAdvancedInfo object */ - public static Matcher matchRatingReviewActionAdvancedInfo( - RatingReviewActionAdvancedInfo ratingReviewActionAdvancedInfo) { + public static Matcher matchScoreReviewActionAdvancedInfo( + ScoreReviewActionAdvancedInfo scoreReviewActionAdvancedInfo) { return Matchers.allOf( - hasJsonPath("$.descriptionRequired", is(ratingReviewActionAdvancedInfo.isDescriptionRequired())), - hasJsonPath("$.maxValue", is(ratingReviewActionAdvancedInfo.getMaxValue())), - hasJsonPath("$.type", is(ratingReviewActionAdvancedInfo.getType())), - hasJsonPath("$.id", is(ratingReviewActionAdvancedInfo.getId())) + hasJsonPath("$.descriptionRequired", is(scoreReviewActionAdvancedInfo.isDescriptionRequired())), + hasJsonPath("$.maxValue", is(scoreReviewActionAdvancedInfo.getMaxValue())), + hasJsonPath("$.type", is(scoreReviewActionAdvancedInfo.getType())), + hasJsonPath("$.id", is(scoreReviewActionAdvancedInfo.getId())) ); } diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 9a839d701ecd..02c618abf420 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1576,7 +1576,6 @@ module_dir = modules # PRIOR to those below (and therefore may override configs in these default # module configuration files). -include = ${module_dir}/actions.cfg include = ${module_dir}/altmetrics.cfg include = ${module_dir}/authentication.cfg include = ${module_dir}/authentication-ip.cfg diff --git a/dspace/config/modules/actions.cfg b/dspace/config/modules/actions.cfg deleted file mode 100644 index 140beaad5b64..000000000000 --- a/dspace/config/modules/actions.cfg +++ /dev/null @@ -1,4 +0,0 @@ -# Reviewer group can be UUID or group name -# This determines the group from which reviewers can be chosen -# If this is not set, the review manager can choose reviewers from all epeople instead of this selected group -action.selectrevieweraction.group = Reviewers diff --git a/dspace/config/modules/workflow.cfg b/dspace/config/modules/workflow.cfg index 8d11df03d5b9..c77050d719ce 100644 --- a/dspace/config/modules/workflow.cfg +++ b/dspace/config/modules/workflow.cfg @@ -16,4 +16,9 @@ workflow.reviewer.file-edit=false # Notify reviewers about tasks returned to the pool -#workflow.notify.returned.tasks = true \ No newline at end of file +#workflow.notify.returned.tasks = true + +# Reviewer group for the select reviewer workflow (can be UUID or group name) +# This determines the group from which reviewers can be chosen +# If this is not set, the review manager can choose reviewers from all e-people instead of this selected group +action.selectrevieweraction.group = Reviewers From b4db57ff99881b35e9697cdb9ea0a7cb6666ee8f Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 7 Feb 2023 10:41:11 +0100 Subject: [PATCH 246/394] [CST-7756] implemented community feedbacks --- .../org/dspace/eperson/FrequencyType.java | 6 +++ .../CollectionUpdates.java} | 4 +- .../CommunityUpdates.java | 2 +- .../impl/CanSubscribeFeature.java | 4 +- .../rest/SubscriptionRestRepositoryIT.java | 14 ----- .../authorization/CanSubscribeFeatureIT.java | 51 +++---------------- .../api/subscriptions_email_configuration.xml | 10 ++-- 7 files changed, 20 insertions(+), 71 deletions(-) rename dspace-api/src/main/java/org/dspace/subscriptions/{dSpaceObjectsUpdates/CollectionsUpdates.java => objectupdates/CollectionUpdates.java} (92%) rename dspace-api/src/main/java/org/dspace/subscriptions/{dSpaceObjectsUpdates => objectupdates}/CommunityUpdates.java (96%) diff --git a/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java index 4dc09b491175..c658e30b12ad 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java +++ b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java @@ -11,6 +11,12 @@ import org.apache.commons.codec.binary.StringUtils; +/** + * This enum holds all the possible frequency types + * that can be used in "subscription-send" script + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com) + */ public enum FrequencyType { DAY("D"), WEEK("W"), diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java similarity index 92% rename from dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java rename to dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java index 974006e7a66d..3fce1bcfcc21 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CollectionsUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.subscriptions.dSpaceObjectsUpdates; +package org.dspace.subscriptions.objectupdates; import java.util.List; @@ -26,7 +26,7 @@ * * @author Alba Aliu */ -public class CollectionsUpdates implements DSpaceObjectUpdates { +public class CollectionUpdates implements DSpaceObjectUpdates { @Autowired private SearchService searchService; diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java similarity index 96% rename from dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java rename to dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java index 40bd1ed30c6e..5495365f551a 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/CommunityUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java @@ -5,7 +5,7 @@ * * http://www.dspace.org/license/ */ -package org.dspace.subscriptions.dSpaceObjectsUpdates; +package org.dspace.subscriptions.objectupdates; import java.util.List; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java index fb1398448691..2e0e27b05751 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java @@ -17,7 +17,6 @@ import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; -import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; @@ -56,8 +55,7 @@ public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLEx public String[] getSupportedTypes() { return new String[]{ CommunityRest.CATEGORY + "." + CommunityRest.NAME, - CollectionRest.CATEGORY + "." + CollectionRest.NAME, - ItemRest.CATEGORY + "." + ItemRest.NAME + CollectionRest.CATEGORY + "." + CollectionRest.NAME }; } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index ad742a0fc1a9..009d2a5fb4af 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -859,8 +859,6 @@ public void putSubscriptionUnauthorizedTest() throws Exception { newSubscription.put("subscriptionParameterList", list); getClient().perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", collection.getID().toString()) - .param("eperson_id", admin.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isUnauthorized()); @@ -890,8 +888,6 @@ public void putSubscriptionForbiddenTest() throws Exception { String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("eperson_id", admin.getID().toString()) - .param("resource", collection.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isForbidden()); @@ -921,8 +917,6 @@ public void putSubscriptionTest() throws Exception { String tokenSubscriber = getAuthToken(eperson.getEmail(), password); getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", collection.getID().toString()) - .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) @@ -958,8 +952,6 @@ public void putSubscriptionInvalidSubscriptionParameterNameTest() throws Excepti String tokenSubscriber = getAuthToken(eperson.getEmail(), password); getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", collection.getID().toString()) - .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isUnprocessableEntity()); @@ -989,8 +981,6 @@ public void putSubscriptionInvalidSubscriptionParameterValueTest() throws Except String tokenSubscriber = getAuthToken(eperson.getEmail(), password); getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", collection.getID().toString()) - .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isUnprocessableEntity()); @@ -1020,8 +1010,6 @@ public void putSubscriptionInvalidSubscriptionTypeValueTest() throws Exception { String tokenSubscriber = getAuthToken(eperson.getEmail(), password); getClient(tokenSubscriber).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", collection.getID().toString()) - .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isUnprocessableEntity()); @@ -1051,8 +1039,6 @@ public void putSubscriptionAdminTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(put("/api/core/subscriptions/" + subscription.getID()) - .param("resource", collection.getID().toString()) - .param("eperson_id", eperson.getID().toString()) .content(objectMapper.writeValueAsString(newSubscription)) .contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java index 10498c273f1a..7eb0960566fc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java @@ -139,41 +139,6 @@ public void canSubscribeCommunityAndCollectionTest() throws Exception { .andExpect(status().isNotFound()); } - @Test - public void canSubscribeItemTest() throws Exception { - context.turnOffAuthorisationSystem(); - Item item = ItemBuilder.createItem(context, collectionAuthorized) - .withTitle("Test item") - .build(); - - ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); - - // define authorizations that we know must exists - Authorization epersonToItem = new Authorization(eperson, canSubscribeFeature, itemRest); - Authorization adminToItem = new Authorization(admin, canSubscribeFeature, itemRest); - - // define authorization that we know not exists - Authorization anonymousToItem = new Authorization(null, canSubscribeFeature, itemRest); - - context.restoreAuthSystemState(); - - String tokenAdmin = getAuthToken(admin.getEmail(), password); - String tokenEPerson = getAuthToken(eperson.getEmail(), password); - - getClient(tokenEPerson).perform(get("/api/authz/authorizations/" + epersonToItem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is( - AuthorizationMatcher.matchAuthorization(epersonToItem)))); - - getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + adminToItem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is( - AuthorizationMatcher.matchAuthorization(adminToItem)))); - - getClient().perform(get("/api/authz/authorizations/" + anonymousToItem.getID())) - .andExpect(status().isNotFound()); - } - @Test public void canNotSubscribeItemTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -196,11 +161,10 @@ public void canNotSubscribeItemTest() throws Exception { ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); - // define authorizations that we know must exists + // define authorization that we know not exists + Authorization anonymousToItem = new Authorization(null, canSubscribeFeature, itemRest); Authorization epersonToItem = new Authorization(eperson, canSubscribeFeature, itemRest); Authorization adminToItem = new Authorization(admin, canSubscribeFeature, itemRest); - - // define authorization that we know not exists Authorization ePersonNotSubscribePermissionToItem = new Authorization(ePersonNotSubscribePermission, canSubscribeFeature, itemRest); @@ -211,17 +175,16 @@ public void canNotSubscribeItemTest() throws Exception { String token3 = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); getClient(token1).perform(get("/api/authz/authorizations/" + epersonToItem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is( - AuthorizationMatcher.matchAuthorization(epersonToItem)))); + .andExpect(status().isNotFound()); getClient(token2).perform(get("/api/authz/authorizations/" + adminToItem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is( - AuthorizationMatcher.matchAuthorization(adminToItem)))); + .andExpect(status().isNotFound()); getClient(token3).perform(get("/api/authz/authorizations/" + ePersonNotSubscribePermissionToItem.getID())) .andExpect(status().isNotFound()); + + getClient().perform(get("/api/authz/authorizations/" + anonymousToItem.getID())) + .andExpect(status().isNotFound()); } @Test diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index e7f071409933..da5ae8e21cc4 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -30,10 +30,6 @@ - - - - @@ -55,12 +51,12 @@ - + - + - + From 8e7cf16ccb0bfa40c08612f8015083da65c732a0 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 7 Feb 2023 12:10:41 +0100 Subject: [PATCH 247/394] [CST-7756] added test that proves what happens if an object is access restricted after you have a subscription to it. --- .../rest/SubscriptionRestRepositoryIT.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index 009d2a5fb4af..038acf7e73a6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -31,6 +31,7 @@ import org.dspace.app.rest.model.SubscriptionParameterRest; import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; @@ -45,6 +46,7 @@ import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; /** @@ -55,6 +57,9 @@ */ public class SubscriptionRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private ResourcePolicyService resourcePolicyService; + private Community subCommunity; private Collection collection; @@ -1199,6 +1204,41 @@ public void linkedDSpaceObjectOfSubscriptionForbiddenTest() throws Exception { .andExpect(status().isForbidden()); } + @Test + public void linkedDSpaceObjectAndRestrictedAccessAfterYouHaveSubscriptionToItTest() throws Exception { + context.turnOffAuthorisationSystem(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1 Test") + .withSubmitterGroup(eperson) + .build(); + + List subscriptionParameterList = new ArrayList<>(); + SubscriptionParameter subscriptionParameter = new SubscriptionParameter(); + subscriptionParameter.setName("frequency"); + subscriptionParameter.setValue("D"); + subscriptionParameterList.add(subscriptionParameter); + Subscription subscription = SubscribeBuilder.subscribeBuilder(context, + "content", col1, eperson, subscriptionParameterList).build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.uuid", Matchers.is(col1.getID().toString()))) + .andExpect(jsonPath("$.name", Matchers.is(col1.getName()))) + .andExpect(jsonPath("$.type", Matchers.is("collection"))); + + context.turnOffAuthorisationSystem(); + // remove all policies for col1 + resourcePolicyService.removeAllPolicies(context, col1); + context.restoreAuthSystemState(); + + // prove that col1 become not accessible + getClient(tokenEPerson).perform(get("/api/core/subscriptions/" + subscription.getID() + "/resource")) + .andExpect(status().isNoContent()); + } + @Test public void linkedDSpaceObjectOfSubscriptionNotFoundTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); From 951c7559435b9772bc904c4c337ec06810000db0 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 7 Feb 2023 12:12:04 +0100 Subject: [PATCH 248/394] [CST-7756] renamed a method --- .../dspace/app/rest/repository/SubscriptionRestRepository.java | 2 +- .../org/dspace/app/rest/security/SubscriptionSecurityBean.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index c136c9ee2d26..600dd1a5cd1e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -143,7 +143,7 @@ public Page findByEPersonAndDso(@Parameter(value = "eperson_id } @Override - @PreAuthorize("@subscriptionSecurity.isEnabelToCreateSubscription(#context) && hasAuthority('AUTHENTICATED')") + @PreAuthorize("@subscriptionSecurity.isAbleToCreateSubscription(#context) && hasAuthority('AUTHENTICATED')") protected SubscriptionRest createAndReturn(Context context) throws SQLException, AuthorizeException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java index 7062b1509f0e..7110fa1d7009 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java @@ -40,7 +40,7 @@ public class SubscriptionSecurityBean { @Autowired private DSpaceObjectUtils dspaceObjectUtil; - public boolean isEnabelToCreateSubscription(Context context) throws SQLException { + public boolean isAbleToCreateSubscription(Context context) throws SQLException { RequestService requestService = new DSpace().getRequestService(); HttpServletRequest req = requestService.getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); From 4ff1194c886f0ac6bea0aa2e01874d7062258d04 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 7 Feb 2023 17:14:10 +0100 Subject: [PATCH 249/394] [CST-7756] updated subscriptions migration scripts --- ...onparamter_change_columns_subscription_table.sql | 13 +++++++------ ...onparamter_change_columns_subscription_table.sql | 13 +++++++------ ...onparamter_change_columns_subscription_table.sql | 13 +++++++------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql index 1ec9d586bfdd..dc187d3c2784 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -11,12 +11,12 @@ ----------------------------------------------------------------------------------- -CREATE SEQUENCE subscription_parameter_seq; +CREATE SEQUENCE if NOT EXISTS subscription_parameter_seq; ------------------------------------------------------- -- Create the subscription_parameter table ------------------------------------------------------- -CREATE TABLE subscription_parameter +CREATE TABLE if NOT EXISTS subscription_parameter ( subscription_parameter_id INTEGER NOT NULL, name CHARACTER VARYING(255), @@ -27,17 +27,18 @@ CREATE TABLE subscription_parameter ); -- -ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; +ALTER TABLE subscription ADD COLUMN if NOT EXISTS dspace_object_id UUID; -- -ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); +ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255); -- +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey; ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- -- UPDATE subscription set dspace_object_id = collection_id , type = 'content'; -- -ALTER TABLE subscription DROP CONSTRAINT Subscription_collection_id_fk; +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS Subscription_collection_id_fk; -- -ALTER TABLE subscription DROP COLUMN collection_id; +ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql index fe81c1743abe..3862830230e3 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -11,11 +11,11 @@ ----------------------------------------------------------------------------------- -CREATE SEQUENCE subscription_parameter_seq; +CREATE SEQUENCE if NOT EXISTS subscription_parameter_seq; ----------------------------------------------------------------------------------- -- ADD table subscription_parameter ----------------------------------------------------------------------------------- -CREATE TABLE subscription_parameter +CREATE TABLE if NOT EXISTS subscription_parameter ( subscription_parameter_id INTEGER NOT NULL, name VARCHAR(255), @@ -27,17 +27,18 @@ CREATE TABLE subscription_parameter ); -- -- -ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; +ALTER TABLE subscription ADD COLUMN if NOT EXISTS dspace_object_id UUID; ---- -- -ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); +ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255); -- UPDATE subscription SET dspace_object_id = collection_id , type = 'content'; -- +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey; ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- -ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_collection_id_fkey; ---- -- -ALTER TABLE subscription DROP COLUMN collection_id; +ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id; -- -- INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id) SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql index f09e26b9f8e5..61e01494fcb3 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -11,11 +11,11 @@ ----------------------------------------------------------------------------------- -CREATE SEQUENCE subscription_parameter_seq; +CREATE SEQUENCE if NOT EXISTS subscription_parameter_seq; ----------------------------------------------------------------------------------- -- ADD table subscription_parameter ----------------------------------------------------------------------------------- -CREATE TABLE subscription_parameter +CREATE TABLE if NOT EXISTS subscription_parameter ( subscription_parameter_id INTEGER NOT NULL, name CHARACTER VARYING(255), @@ -25,17 +25,18 @@ CREATE TABLE subscription_parameter CONSTRAINT subscription_parameter_subscription_fkey FOREIGN KEY (subscription_id) REFERENCES subscription (subscription_id) ON DELETE CASCADE ); -- -ALTER TABLE subscription ADD COLUMN dspace_object_id UUID; +ALTER TABLE subscription ADD COLUMN if NOT EXISTS dspace_object_id UUID; -- -- -ALTER TABLE subscription ADD COLUMN type CHARACTER VARYING(255); +ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255); ---- -- +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey; ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- UPDATE subscription SET dspace_object_id = collection_id , type = 'content'; -- -ALTER TABLE subscription DROP CONSTRAINT subscription_collection_id_fkey; +ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_collection_id_fkey; -- -- -ALTER TABLE subscription DROP COLUMN collection_id; +ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id; -- -- INSERT INTO subscription_parameter (subscription_parameter_id, name, value, subscription_id) SELECT getnextid('subscription_parameter'), 'frequency', 'D', subscription_id from "subscription" ; From 9286a692c47bb3bb8575b3a090cc1c4bcd77b3c1 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 7 Feb 2023 17:15:37 +0100 Subject: [PATCH 250/394] [CST-7756] removed 'lastModified_dt' index field --- .../dspace/subscriptions/objectupdates/CollectionUpdates.java | 2 +- .../dspace/subscriptions/objectupdates/CommunityUpdates.java | 2 +- dspace/solr/search/conf/schema.xml | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java index 3fce1bcfcc21..83ec59e4535b 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java @@ -38,7 +38,7 @@ public List findUpdates(Context context, DSpaceObject dSpaceObj DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.addFilterQueries("search.resourcetype:" + Item.class.getSimpleName()); discoverQuery.addFilterQueries("location.coll:(" + dSpaceObject.getID() + ")"); - discoverQuery.addFilterQueries("lastModified_dt:" + findLastFrequency(frequency)); + discoverQuery.addFilterQueries("lastModified:" + findLastFrequency(frequency)); DiscoverResult discoverResult = searchService.search(context, discoverQuery); return discoverResult.getIndexableObjects(); } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java index 5495365f551a..335734d61cc1 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java @@ -38,7 +38,7 @@ public List findUpdates(Context context, DSpaceObject dSpaceObj DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.addFilterQueries("search.resourcetype:" + Item.class.getSimpleName()); discoverQuery.addFilterQueries("location.comm:(" + dSpaceObject.getID() + ")"); - discoverQuery.addFilterQueries("lastModified_dt:" + this.findLastFrequency(frequency)); + discoverQuery.addFilterQueries("lastModified:" + this.findLastFrequency(frequency)); DiscoverResult discoverResult = searchService.search(context, discoverQuery); return discoverResult.getIndexableObjects(); } diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index 2232ba5a503f..caa646ba1b8b 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -283,7 +283,6 @@ - @@ -359,6 +358,5 @@ - From f6967125fb64d6a4586c48d553229a5b6b5886cf Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 7 Feb 2023 17:16:21 +0100 Subject: [PATCH 251/394] [CST-7756] minor refactoring --- .../dspace/subscriptions/ContentGenerator.java | 10 ++++++---- .../api/subscriptions_email_configuration.xml | 17 +++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index 9b3f8cd185e9..78cac84b9423 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -16,7 +16,6 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; -import javax.annotation.Resource; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -42,8 +41,7 @@ public class ContentGenerator implements SubscriptionGenerator private final Logger log = LogManager.getLogger(ContentGenerator.class); @SuppressWarnings("unchecked") - @Resource(name = "entityDissemination") - private Map mapEntityDisseminatorProperty = new HashMap(); + private Map entityType2Disseminator = new HashMap(); @Autowired private ItemService itemService; @@ -76,7 +74,7 @@ private String generateBodyMail(Context context, List indexable out.write("\n".getBytes(UTF_8)); Item item = (Item) indexableObject.getIndexedObject(); String entityType = itemService.getEntityTypeLabel(item); - mapEntityDisseminatorProperty.get(entityType).disseminate(context, item, out); + entityType2Disseminator.get(entityType).disseminate(context, item, out); } return out.toString(); } else { @@ -89,4 +87,8 @@ private String generateBodyMail(Context context, List indexable return EMPTY; } + public void setEntityType2Disseminator(Map entityType2Disseminator) { + this.entityType2Disseminator = entityType2Disseminator; + } + } diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index da5ae8e21cc4..4740a2d4e300 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -30,16 +30,9 @@ - - - - - - - - - dc.title - + + + @@ -55,6 +48,10 @@ + + dc.title + + From 35846335165c046cae67dc63a6fbcec5d3e887f7 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Tue, 7 Feb 2023 18:00:09 +0100 Subject: [PATCH 252/394] [CST-7756] added email subject --- dspace/config/emails/subscriptions_content | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/emails/subscriptions_content b/dspace/config/emails/subscriptions_content index f4d557e1f778..86612bac54f2 100644 --- a/dspace/config/emails/subscriptions_content +++ b/dspace/config/emails/subscriptions_content @@ -2,6 +2,7 @@ ## ## Parameters: {0} Collections updates ## {1} Communities updates +#set($subject = 'DSpace Subscription') This email is sent from DSpace based on the chosen subscription preferences. From 31bd28e80cbf46ae4de5e75fc49cb84c1f870969 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Tue, 7 Feb 2023 18:00:26 +0100 Subject: [PATCH 253/394] [CST-7756] added dissemination for generic DSpace Item --- .../main/java/org/dspace/subscriptions/ContentGenerator.java | 4 +++- .../config/spring/api/subscriptions_email_configuration.xml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index 78cac84b9423..78a550c10585 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Objects; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.Item; @@ -73,7 +74,8 @@ private String generateBodyMail(Context context, List indexable for (IndexableObject indexableObject : indexableObjects) { out.write("\n".getBytes(UTF_8)); Item item = (Item) indexableObject.getIndexedObject(); - String entityType = itemService.getEntityTypeLabel(item); + String entityType = StringUtils.defaultIfBlank(itemService.getEntityTypeLabel(item), + "Item"); entityType2Disseminator.get(entityType).disseminate(context, item, out); } return out.toString(); diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index 4740a2d4e300..348500ee3f65 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -36,6 +36,7 @@ + From 6190e337acdd8d61c23b94d22585b21cfeed6de8 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Tue, 7 Feb 2023 18:52:35 +0100 Subject: [PATCH 254/394] [CST-7756] If no crosswalk is defined for the Item's entity type, default generic Item one is applied --- .../java/org/dspace/subscriptions/ContentGenerator.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index 78a550c10585..a913f2504a50 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -16,8 +16,8 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.Item; @@ -74,9 +74,10 @@ private String generateBodyMail(Context context, List indexable for (IndexableObject indexableObject : indexableObjects) { out.write("\n".getBytes(UTF_8)); Item item = (Item) indexableObject.getIndexedObject(); - String entityType = StringUtils.defaultIfBlank(itemService.getEntityTypeLabel(item), - "Item"); - entityType2Disseminator.get(entityType).disseminate(context, item, out); + String entityType = itemService.getEntityTypeLabel(item); + Optional.ofNullable(entityType2Disseminator.get(entityType)) + .orElseGet(() -> entityType2Disseminator.get("Item")) + .disseminate(context, item, out); } return out.toString(); } else { From 9aefeebaa35494fc7a55c12da2bf7e88296061f4 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 7 Feb 2023 22:01:41 +0100 Subject: [PATCH 255/394] IT fix: ReviewManagers repository wide group, not collection workflow group --- .../app/rest/TaskRestRepositoriesIT.java | 132 ++++++++++-------- 1 file changed, 70 insertions(+), 62 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java index cf6ee29f6971..393e1b2119f7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/TaskRestRepositoriesIT.java @@ -4353,7 +4353,7 @@ public void selectReviewerWorkflowTest() throws Exception { .withEmail("creator@example.com") .withPassword(password).build(); - // Create with some members to be added to "ReviewManagers" on collection creation + // Create with some members to be added to "ReviewManagers" EPerson reviewManager1 = EPersonBuilder.createEPerson(context) .withEmail("reviewManager1@example.com") .withPassword(password).build(); @@ -4366,6 +4366,15 @@ public void selectReviewerWorkflowTest() throws Exception { .withEmail("reviewManager3@example.com") .withPassword(password).build(); + // The "selectSingleReviewer" requires the "ReviewManagers" repository group to be present with at least 1 + // member + GroupBuilder.createGroup(context) + .withName("ReviewManagers") + .addMember(reviewManager1) + .addMember(reviewManager2) + .addMember(reviewManager3) + .build(); + // Create "Reviewers" with some members Group reviewerGroup = GroupBuilder.createGroup(context).withName("Reviewers").build(); @@ -4391,12 +4400,11 @@ public void selectReviewerWorkflowTest() throws Exception { Collection collection = CollectionBuilder.createCollection(context, parentCommunity, "123456789/workflow-test-1") .withName("Collection 1") - .withWorkflowGroup("reviewmanagers", reviewManager1, reviewManager2, reviewManager3, creator) .build(); // Create 3 pool tasks // First one for selecting a single reviewer - PoolTask poolTask1 = PoolTaskBuilder.createPoolTask(context, collection, creator) + PoolTask poolTask1 = PoolTaskBuilder.createPoolTask(context, collection, reviewManager1) .withTitle("Workflow Item 1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") @@ -4404,7 +4412,7 @@ public void selectReviewerWorkflowTest() throws Exception { .withSubject("ExtraEntry").build(); XmlWorkflowItem witem1 = poolTask1.getWorkflowItem(); // Second one for selecting multiple reviewers - PoolTask poolTask2 = PoolTaskBuilder.createPoolTask(context, collection, creator) + PoolTask poolTask2 = PoolTaskBuilder.createPoolTask(context, collection, reviewManager2) .withTitle("Workflow Item 2") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") @@ -4412,7 +4420,7 @@ public void selectReviewerWorkflowTest() throws Exception { .withSubject("ExtraEntry").build(); XmlWorkflowItem witem2 = poolTask2.getWorkflowItem(); // Third one for trying to add user not in "Reviewers" group - PoolTask poolTask3 = PoolTaskBuilder.createPoolTask(context, collection, creator) + PoolTask poolTask3 = PoolTaskBuilder.createPoolTask(context, collection, reviewManager3) .withTitle("Workflow Item 3") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") @@ -4446,9 +4454,9 @@ public void selectReviewerWorkflowTest() throws Exception { // Verify as member of "ReviewManagers" you can claim in this tasks getClient(reviewManager1Token).perform(post("/api/workflow/claimedtasks") - .contentType( - MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) - .content("/api/workflow/pooltasks/" + poolTask1.getID())) + .contentType( + MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/workflow/pooltasks/" + poolTask1.getID())) .andExpect(status().isCreated()) .andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask"))))) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); @@ -4459,18 +4467,18 @@ public void selectReviewerWorkflowTest() throws Exception { // Verify items now in claimed tasks /api/workflow/claimedtasks for user reviewManager1 getClient(reviewManager1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewManager1.getID().toString())) + .param("uuid", reviewManager1.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( Matchers.allOf( hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), hasJsonPath("$.type", Matchers.is("claimedtask")), hasJsonPath("$._embedded.owner", - Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewManager1.getEmail()))), + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewManager1.getEmail()))), hasJsonPath("$._embedded.action.id", Matchers.is("selectrevieweraction")), hasJsonPath("$._embedded.workflowitem", - Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))) + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -4478,7 +4486,7 @@ public void selectReviewerWorkflowTest() throws Exception { // Verify items now not in claimed tasks /api/workflow/claimedtasks for user reviewer1 getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer1.getID().toString())) + .param("uuid", reviewer1.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -4488,25 +4496,25 @@ public void selectReviewerWorkflowTest() throws Exception { SelectReviewerAction.resetGroup(); // Select reviewer1 as a reviewer, wf step 1 getClient(reviewManager1Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) - .param("submit_select_reviewer", "true") - .param("eperson", reviewer1.getID().toString()) - .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .param("submit_select_reviewer", "true") + .param("eperson", reviewer1.getID().toString()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) .andExpect(status().isNoContent()); // Verify reviewer1 has the claimed task getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer1.getID().toString())) + .param("uuid", reviewer1.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( Matchers.allOf( hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), hasJsonPath("$.type", Matchers.is("claimedtask")), hasJsonPath("$._embedded.owner", - Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer1.getEmail()))), + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer1.getEmail()))), hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")), hasJsonPath("$._embedded.workflowitem", - Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))) + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -4514,39 +4522,39 @@ public void selectReviewerWorkflowTest() throws Exception { // Verify other members of "Reviewers" don't have this task claimed getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer2.getID().toString())) + .param("uuid", reviewer2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer3.getID().toString())) + .param("uuid", reviewer3.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); // Verify members of "ReviewManagers" don't have this task claimed getClient(reviewManager1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewManager1.getID().toString())) + .param("uuid", reviewManager1.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); getClient(reviewManager2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewManager2.getID().toString())) + .param("uuid", reviewManager2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); getClient(reviewManager3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewManager3.getID().toString())) + .param("uuid", reviewManager3.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); // Verify other users don't have this task claimed getClient(userToken).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", user.getID().toString())) + .param("uuid", user.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -4556,35 +4564,35 @@ public void selectReviewerWorkflowTest() throws Exception { // Claim pooltask2 as member of "ReviewManagers" getClient(reviewManager2Token).perform(post("/api/workflow/claimedtasks") - .contentType( - MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) - .content("/api/workflow/pooltasks/" + poolTask2.getID())) + .contentType( + MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/workflow/pooltasks/" + poolTask2.getID())) .andExpect(status().isCreated()) .andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask"))))) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); // Select reviewer2 and reviewer3 as reviewers, wf step 1 getClient(reviewManager2Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) - .param("submit_select_reviewer", "true") - .param("eperson", reviewer2.getID().toString()) - .param("eperson", reviewer3.getID().toString()) - .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .param("submit_select_reviewer", "true") + .param("eperson", reviewer2.getID().toString()) + .param("eperson", reviewer3.getID().toString()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) .andExpect(status().isNoContent()); // Verify reviewer2 has the claimed task getClient(reviewer2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer2.getID().toString())) + .param("uuid", reviewer2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( Matchers.allOf( hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), hasJsonPath("$.type", Matchers.is("claimedtask")), hasJsonPath("$._embedded.owner", - Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer2.getEmail()))), + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer2.getEmail()))), hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")), hasJsonPath("$._embedded.workflowitem", - Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry"))) + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry"))) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -4592,18 +4600,18 @@ public void selectReviewerWorkflowTest() throws Exception { // Verify reviewer3 has the claimed task too getClient(reviewer3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer3.getID().toString())) + .param("uuid", reviewer3.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( Matchers.allOf( hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), hasJsonPath("$.type", Matchers.is("claimedtask")), hasJsonPath("$._embedded.owner", - Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer3.getEmail()))), + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer3.getEmail()))), hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")), hasJsonPath("$._embedded.workflowitem", - Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry"))) + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry"))) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -4611,47 +4619,47 @@ public void selectReviewerWorkflowTest() throws Exception { // Verify reviewer1 of "Reviewers" doesn't have this task claimed, only the first task getClient(reviewer1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewer1.getID().toString())) + .param("uuid", reviewer1.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( Matchers.allOf( hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), hasJsonPath("$.type", Matchers.is("claimedtask")), hasJsonPath("$._embedded.owner", - Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer1.getEmail()))), + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewer1.getEmail()))), hasJsonPath("$._embedded.action.id", Matchers.is("singleuserreviewaction")), hasJsonPath("$._embedded.workflowitem", - Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))), + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem1, "Workflow Item 1", "2017-10-17", "ExtraEntry"))), hasJsonPath("$._embedded.workflowitem", - Matchers.not(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry"))) + Matchers.not(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem2, "Workflow Item 2", "2017-10-17", "ExtraEntry"))) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(1))); // Verify members of "ReviewManagers" don't have this task claimed getClient(reviewManager1Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewManager1.getID().toString())) + .param("uuid", reviewManager1.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); getClient(reviewManager2Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewManager2.getID().toString())) + .param("uuid", reviewManager2.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); getClient(reviewManager3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewManager3.getID().toString())) + .param("uuid", reviewManager3.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.totalElements", is(0))); // Verify other users don't have this task claimed getClient(userToken).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", user.getID().toString())) + .param("uuid", user.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -4661,23 +4669,23 @@ public void selectReviewerWorkflowTest() throws Exception { // Claim pooltask3 as member of "ReviewManagers" getClient(reviewManager3Token).perform(post("/api/workflow/claimedtasks") - .contentType( - MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) - .content("/api/workflow/pooltasks/" + poolTask3.getID())) + .contentType( + MediaType.parseMediaType(RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("/api/workflow/pooltasks/" + poolTask3.getID())) .andExpect(status().isCreated()) .andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask"))))) .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); // Select (non-reviewer) user as a reviewer, wf step 1 getClient(reviewManager3Token).perform(post("/api/workflow/claimedtasks/" + idRef.get()) - .param("submit_select_reviewer", "true") - .param("eperson", user.getID().toString()) - .contentType(MediaType.APPLICATION_FORM_URLENCODED)) + .param("submit_select_reviewer", "true") + .param("eperson", user.getID().toString()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED)) .andExpect(status().isNoContent()); // Verify user does not have this task claimed getClient(userToken).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", user.getID().toString())) + .param("uuid", user.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) @@ -4685,18 +4693,18 @@ public void selectReviewerWorkflowTest() throws Exception { // Verify item still in claimed tasks for user reviewManager3 on step "selectrevieweraction" getClient(reviewManager3Token).perform(get("/api/workflow/claimedtasks/search/findByUser") - .param("uuid", reviewManager3.getID().toString())) + .param("uuid", reviewManager3.getID().toString())) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.claimedtasks", Matchers.contains( Matchers.allOf( hasJsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks/")), hasJsonPath("$.type", Matchers.is("claimedtask")), hasJsonPath("$._embedded.owner", - Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewManager3.getEmail()))), + Matchers.is(EPersonMatcher.matchEPersonOnEmail(reviewManager3.getEmail()))), hasJsonPath("$._embedded.action.id", Matchers.is("selectrevieweraction")), hasJsonPath("$._embedded.workflowitem", - Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( - witem3, "Workflow Item 3", "2017-10-17", "ExtraEntry"))) + Matchers.is(WorkflowItemMatcher.matchItemWithTitleAndDateIssuedAndSubject( + witem3, "Workflow Item 3", "2017-10-17", "ExtraEntry"))) )))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/workflow/claimedtasks"))) .andExpect(jsonPath("$.page.size", is(20))) From 25164603641d3f8175fa29e8d9f44409afe1c6f2 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 7 Feb 2023 00:04:49 +0100 Subject: [PATCH 256/394] Test IT without drop constraint --- ...1.11__xml_workflow_migration_multiple_reviewers.sql | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.2_2023.01.11__xml_workflow_migration_multiple_reviewers.sql diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.2_2023.01.11__xml_workflow_migration_multiple_reviewers.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.2_2023.01.11__xml_workflow_migration_multiple_reviewers.sql deleted file mode 100644 index af397b6471e4..000000000000 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.2_2023.01.11__xml_workflow_migration_multiple_reviewers.sql +++ /dev/null @@ -1,10 +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/ --- - --- remove constraint so multiple reviewers can be selected for the same workflowitem -ALTER TABLE cwf_in_progress_user DROP CONSTRAINT cwf_in_progress_user_unique; From ae919d6117e4280fef18e6ac54a1136194a0c3c8 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 8 Feb 2023 12:48:07 +0100 Subject: [PATCH 257/394] [CST-7756] restores security check as was originaly --- .../SubscriptionRestRepository.java | 19 ++++- .../security/SubscriptionSecurityBean.java | 75 ------------------- 2 files changed, 18 insertions(+), 76 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index 600dd1a5cd1e..ce1bcff11fbc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -11,6 +11,7 @@ import static org.dspace.app.rest.model.SubscriptionRest.NAME; import static org.dspace.core.Constants.COLLECTION; import static org.dspace.core.Constants.COMMUNITY; +import static org.dspace.core.Constants.READ; import java.io.IOException; import java.sql.SQLException; @@ -36,6 +37,7 @@ import org.dspace.app.rest.model.SubscriptionRest; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -68,6 +70,8 @@ public class SubscriptionRestRepository extends DSpaceRestRepository findByEPersonAndDso(@Parameter(value = "eperson_id } @Override - @PreAuthorize("@subscriptionSecurity.isAbleToCreateSubscription(#context) && hasAuthority('AUTHENTICATED')") + @PreAuthorize("isAuthenticated()") protected SubscriptionRest createAndReturn(Context context) throws SQLException, AuthorizeException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); String epersonId = req.getParameter("eperson_id"); @@ -160,6 +164,19 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, throw new DSpaceBadRequestException("Id of person or dspace object must represents reals ids"); } + // user must have read permissions to dSpaceObject object + if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, READ, true)) { + throw new AuthorizeException("The user has not READ rights on this DSO"); + } + + // if user is Admin don't make this control, + // otherwise make this control because normal user can only subscribe with their own ID of user. + if (!authorizeService.isAdmin(context)) { + if (!ePerson.equals(context.getCurrentUser())) { + throw new AuthorizeException("Only administrator can subscribe for other persons"); + } + } + if (dSpaceObject.getType() == COMMUNITY || dSpaceObject.getType() == COLLECTION) { Subscription subscription = null; ServletInputStream input = req.getInputStream(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java deleted file mode 100644 index 7110fa1d7009..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubscriptionSecurityBean.java +++ /dev/null @@ -1,75 +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.security; - -import java.sql.SQLException; -import java.util.Objects; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; - -import org.dspace.app.rest.utils.DSpaceObjectUtils; -import org.dspace.authorize.service.AuthorizeService; -import org.dspace.content.DSpaceObject; -import org.dspace.core.Constants; -import org.dspace.core.Context; -import org.dspace.eperson.EPerson; -import org.dspace.eperson.service.EPersonService; -import org.dspace.services.RequestService; -import org.dspace.utils.DSpace; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * Methods of this class are used on PreAuthorize annotations - * to check security on subscriptions endpoint - * - * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it) - */ -@Component(value = "subscriptionSecurity") -public class SubscriptionSecurityBean { - - @Autowired - private EPersonService ePersonService; - @Autowired - private AuthorizeService authorizeService; - @Autowired - private DSpaceObjectUtils dspaceObjectUtil; - - public boolean isAbleToCreateSubscription(Context context) throws SQLException { - RequestService requestService = new DSpace().getRequestService(); - HttpServletRequest req = requestService.getCurrentRequest().getHttpServletRequest(); - String epersonId = req.getParameter("eperson_id"); - String dsoId = req.getParameter("resource"); - - if (Objects.isNull(dsoId) || Objects.isNull(epersonId)) { - return true; - } - - try { - DSpaceObject dSpaceObject = dspaceObjectUtil.findDSpaceObject(context, UUID.fromString(dsoId)); - EPerson ePerson = ePersonService.findByIdOrLegacyId(context, epersonId); - if (Objects.isNull(ePerson) || Objects.isNull(dSpaceObject)) { - return true; - } - - if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, Constants.READ, true)) { - return false; - } - - if (!authorizeService.isAdmin(context)) { - if (!ePerson.equals(context.getCurrentUser())) { - return false; - } - } - } catch (SQLException sqlException) { - throw new SQLException(sqlException.getMessage(), sqlException); - } - return true; - } - -} From a00295a32d4c4c30f514dbda0e2ff2fbdfe755e7 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 8 Feb 2023 13:22:50 +0100 Subject: [PATCH 258/394] [CST-7756] described beans --- .../spring/api/subscriptions_email_configuration.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index 348500ee3f65..2d52dfc27089 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -18,11 +18,16 @@ + + @@ -34,6 +39,8 @@ + @@ -49,6 +56,7 @@ + dc.title From cef47deddcf6ca8af83923b54c2e7f89991ff5da Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 8 Feb 2023 10:40:24 +0100 Subject: [PATCH 259/394] #8628: Remove GET shortlivedtokens endpoint & related IT --- .../rest/AuthenticationRestController.java | 29 +------ .../rest/AuthenticationRestControllerIT.java | 83 ------------------- 2 files changed, 1 insertion(+), 111 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java index 313fe2de6017..e8b8eb8e70da 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java @@ -34,7 +34,6 @@ import org.dspace.app.rest.security.RestAuthenticationService; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; -import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.service.ClientInfoService; import org.slf4j.Logger; @@ -210,33 +209,7 @@ public AuthenticationTokenResource shortLivedToken(HttpServletRequest request) { } /** - * This method will generate a short lived token to be used for bitstream downloads among other things. - * - * For security reasons, this endpoint only responds to a explicitly defined list of ips. - * - * curl -v -X GET https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo" - * - * Example: - *
-     * {@code
-     * curl -v -X GET https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo"
-     * }
-     * 
- * @param request The StandardMultipartHttpServletRequest - * @return The created short lived token - */ - @PreAuthorize("hasAuthority('AUTHENTICATED')") - @RequestMapping(value = "/shortlivedtokens", method = RequestMethod.GET) - public AuthenticationTokenResource shortLivedTokenViaGet(HttpServletRequest request) throws AuthorizeException { - if (!clientInfoService.isRequestFromTrustedProxy(request.getRemoteAddr())) { - throw new AuthorizeException("Requests to this endpoint should be made from a trusted IP address."); - } - - return shortLivedTokenResponse(request); - } - - /** - * See {@link #shortLivedToken} and {@link #shortLivedTokenViaGet} + * See {@link #shortLivedToken} */ private AuthenticationTokenResource shortLivedTokenResponse(HttpServletRequest request) { Projection projection = utils.obtainProjection(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java index 19f0d9d26773..69e70dbb087e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthenticationRestControllerIT.java @@ -1394,64 +1394,6 @@ public void testShortLivedToken() throws Exception { .andExpect(status().isNoContent()); } - @Test - public void testShortLivedTokenUsingGet() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - - // Verify the main session salt doesn't change - String salt = eperson.getSessionSalt(); - - getClient(token).perform( - get("/api/authn/shortlivedtokens") - .with(ip(TRUSTED_IP)) - ) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token", notNullValue())) - .andExpect(jsonPath("$.type", is("shortlivedtoken"))) - .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/authn/shortlivedtokens"))) - // Verify generating short-lived token doesn't change our CSRF token - // (so, neither the CSRF cookie nor header are sent back) - .andExpect(cookie().doesNotExist("DSPACE-XSRF-COOKIE")) - .andExpect(header().doesNotExist("DSPACE-XSRF-TOKEN")); - - assertEquals(salt, eperson.getSessionSalt()); - - // Logout, invalidating token - getClient(token).perform(post("/api/authn/logout")) - .andExpect(status().isNoContent()); - } - - @Test - public void testShortLivedTokenUsingGetFromUntrustedIpShould403() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - - getClient(token).perform( - get("/api/authn/shortlivedtokens") - .with(ip(UNTRUSTED_IP)) - ) - .andExpect(status().isForbidden()); - - // Logout, invalidating token - getClient(token).perform(post("/api/authn/logout")) - .andExpect(status().isNoContent()); - } - - @Test - public void testShortLivedTokenUsingGetFromUntrustedIpWithForwardHeaderShould403() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - - getClient(token).perform( - get("/api/authn/shortlivedtokens") - .with(ip(UNTRUSTED_IP)) - .header("X-Forwarded-For", TRUSTED_IP) // this should not affect the test result - ) - .andExpect(status().isForbidden()); - - // Logout, invalidating token - getClient(token).perform(post("/api/authn/logout")) - .andExpect(status().isNoContent()); - } - @Test public void testShortLivedTokenWithCSRFSentViaParam() throws Exception { String token = getAuthToken(eperson.getEmail(), password); @@ -1476,15 +1418,6 @@ public void testShortLivedTokenNotAuthenticated() throws Exception { .andExpect(status().isUnauthorized()); } - @Test - public void testShortLivedTokenNotAuthenticatedUsingGet() throws Exception { - getClient().perform( - get("/api/authn/shortlivedtokens") - .with(ip(TRUSTED_IP)) - ) - .andExpect(status().isUnauthorized()); - } - @Test public void testShortLivedTokenToDownloadBitstream() throws Exception { Bitstream bitstream = createPrivateBitstream(); @@ -1598,22 +1531,6 @@ public void testGenerateShortLivedTokenWithShortLivedToken() throws Exception { .andExpect(status().isNoContent()); } - @Test - public void testGenerateShortLivedTokenWithShortLivedTokenUsingGet() throws Exception { - String token = getAuthToken(eperson.getEmail(), password); - String shortLivedToken = getShortLivedToken(token); - - getClient().perform( - get("/api/authn/shortlivedtokens?authentication-token=" + shortLivedToken) - .with(ip(TRUSTED_IP)) - ) - .andExpect(status().isForbidden()); - - // Logout, invalidating token - getClient(token).perform(post("/api/authn/logout")) - .andExpect(status().isNoContent()); - } - @Test public void testStatusOrcidAuthenticatedWithCookie() throws Exception { From c93e6e4a9eeaf50cd601a3a09a7af35691a499a7 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 14 Dec 2021 14:32:29 +1300 Subject: [PATCH 260/394] [TLC-249] Show Identifier Step, pre-register identifiers --- .../content/WorkspaceItemServiceImpl.java | 16 ++ .../dspace/identifier/doi/DOIConsumer.java | 50 +++++- .../dspaceFolder/config/item-submission.xml | 8 + .../app/rest/model/step/DataIdentifiers.java | 55 ++++++ .../app/rest/submit/DataProcessingStep.java | 1 + .../rest/submit/step/ShowIdentifiersStep.java | 160 ++++++++++++++++++ .../dspaceFolder/config/item-submission.xml | 9 + .../config/spring/api/identifier-service.xml | 46 +++++ .../rest/SubmissionShowIdentifiersRestIT.java | 123 ++++++++++++++ dspace/config/dspace.cfg | 1 + dspace/config/item-submission.xml | 7 + dspace/config/modules/identifiers.cfg | 11 ++ 12 files changed, 483 insertions(+), 4 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java create mode 100644 dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java create mode 100644 dspace/config/modules/identifiers.cfg diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index f40bb5256f3b..32be655dae9a 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -24,6 +24,7 @@ import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.WorkspaceItemDAO; +import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; @@ -32,6 +33,9 @@ import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.event.Event; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowService; import org.springframework.beans.factory.annotation.Autowired; @@ -160,6 +164,18 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b } itemService.update(context, item); + + // If configured, register identifiers (eg handle, DOI) now. This is typically used with the Show Identifiers + // submission step which previews minted handles and DOIs during the submission process. Default: false + if (DSpaceServicesFactory.getInstance().getConfigurationService() + .getBooleanProperty("identifiers.submission.register", false)) { + try { + IdentifierServiceFactory.getInstance().getIdentifierService().register(context, item); + } catch (IdentifierException e) { + log.error("Could not register identifier(s) for item {}: {}", item.getID(), e.getMessage()); + } + } + workspaceItem.setItem(item); log.info(LogHelper.getHeader(context, "create_workspace_item", diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java index 654d275d8725..622b546defde 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java @@ -18,6 +18,7 @@ import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.IdentifierNotFoundException; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -63,12 +64,13 @@ public void consume(Context ctx, Event event) throws Exception { } Item item = (Item) dso; + /* if (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) != null || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null) { // ignore workflow and workspace items, DOI will be minted when item is installed return; } - + */ DOIIdentifierProvider provider = new DSpace().getSingletonService( DOIIdentifierProvider.class); @@ -80,9 +82,49 @@ public void consume(Context ctx, Event event) throws Exception { // items without dois. } if (doi == null) { - log.debug("DOIConsumer cannot handles items without DOIs, skipping: " - + event.toString()); - return; + // if the item is workflow or workspace, there is a special case here - the ShowIdentifersStep + // needs this consumer to reserve DOIs in the event that the item now meets criteria for a pre-mint + if (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) != null + || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null) { + if (DSpaceServicesFactory.getInstance().getConfigurationService() + .getBooleanProperty("identifiers.submission.register", false)) { + try { + String newDoi = provider.mint(ctx, item); + if (newDoi != null) { + provider.reserve(ctx, item, newDoi); + log.debug("Consumer minting and reserving new DOI for in-progress item: " + newDoi); + } else { + return; + } + } catch (DOIIdentifierNotApplicableException e) { + log.debug("Consumer skipping mint for item as it was filtered out: " + item.getID()); + } + } else { + log.debug("Consumer skipping mint for item as it is in-progress and" + + "identifiers.submission.register=false: " + item.getID()); + } + } else { + log.debug("DOIConsumer cannot handles items without DOIs, skipping: " + + event.toString()); + return; + } + } else if (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) != null + || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null) { + if (DSpaceServicesFactory.getInstance().getConfigurationService() + .getBooleanProperty("identifiers.submission.register", false)) { + // We have a DOI for an in-progress submission item -- if the filter says "no", we should delete + // the minted DOI so it doesn't get registered on item install. + try { + provider.checkMintable(ctx, item); + } catch (DOIIdentifierNotApplicableException e) { + log.debug("Consumer deleting DOI for in-progress item: " + doi); + provider.delete(ctx, item, doi); + return; + } + } else { + log.debug("Consumer skipping mint for item as it is in-progress and" + + "identifiers.submission.register=false: " + item.getID()); + } } try { provider.updateMetadata(ctx, dso, doi); diff --git a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml index f40298db3014..6d8ae0c2f0d9 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -143,6 +143,12 @@ org.dspace.app.rest.submit.step.SherpaPolicyStep sherpaPolicy + + + submit.progressbar.identifiers + org.dspace.app.rest.submit.step.ShowIdentifiersStep + identifiers + @@ -169,6 +175,8 @@ + + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java new file mode 100644 index 000000000000..c487d3769ebc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java @@ -0,0 +1,55 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.step; + +import java.util.List; + +/** + * Java bean with basic DOI / Handle / other identifier data for + * display in submission step + * + * @author Kim Shepherd (kim@shepherd.nz) + */ +public class DataIdentifiers implements SectionData { + String handle; + String doi; + List otherIdentifiers; + + public String getHandle() { + return handle; + } + + public void setHandle(String handle) { + this.handle = handle; + } + + public String getDoi() { + return doi; + } + + public void setDoi(String doi) { + this.doi = doi; + } + + public List getOtherIdentifiers() { + return otherIdentifiers; + } + + public void setOtherIdentifiers(List otherIdentifiers) { + this.otherIdentifiers = otherIdentifiers; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Handle: ").append(handle); + sb.append("DOI: ").append(doi); + sb.append("Others: ").append(String.join(", ", otherIdentifiers)); + return sb.toString(); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java index 8eb03acde008..99af309cdb9a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/DataProcessingStep.java @@ -38,6 +38,7 @@ public interface DataProcessingStep extends RestProcessingStep { public static final String CCLICENSE_STEP_OPERATION_ENTRY = "cclicense/uri"; public static final String ACCESS_CONDITION_STEP_OPERATION_ENTRY = "discoverable"; public static final String ACCESS_CONDITION_POLICY_STEP_OPERATION_ENTRY = "accessConditions"; + public static final String SHOW_IDENTIFIERS_ENTRY = "identifiers"; public static final String UPLOAD_STEP_METADATA_PATH = "metadata"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java new file mode 100644 index 000000000000..2bdfbbde80ef --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java @@ -0,0 +1,160 @@ +/** + * 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.submit.step; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.step.DataIdentifiers; +import org.dspace.app.rest.submit.AbstractProcessingStep; +import org.dspace.app.rest.submit.SubmissionService; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.identifier.DOI; +import org.dspace.identifier.Handle; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.IdentifierService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.services.model.Request; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Submission processing step to return identifier data for use in the 'identifiers' submission section component + * in dspace-angular. For effective use, the "identifiers.submission.register" configuration property + * in identifiers.cfg should be enabled so that the WorkspaceItemService will register identifiers for the new item + * at the time of creation, and the DOI consumer will allow workspace and workflow items to have their DOIs minted + * or deleted as per item filter results. + * + * This method can be extended to allow (if authorised) an operation to be sent which will + * override an item filter and force reservation of an identifier. + * + * @author Kim Shepherd + */ +public class ShowIdentifiersStep extends AbstractProcessingStep { + + private static final Logger log = LogManager.getLogger(ShowIdentifiersStep.class); + + @Autowired(required = true) + protected HandleService handleService; + @Autowired(required = true) + protected ContentServiceFactory contentServiceFactory; + + /** + * Override DataProcessing.getData, return data identifiers from getIdentifierData() + * + * @param submissionService The submission service + * @param obj The workspace or workflow item + * @param config The submission step configuration + * @return A simple DataIdentifiers bean containing doi, handle and list of other identifiers + */ + @Override + public DataIdentifiers getData(SubmissionService submissionService, InProgressSubmission obj, + SubmissionStepConfig config) throws Exception { + // If configured, WorkspaceItemService item creation will also call IdentifierService.register() + // for the new item, and the DOI consumer (if also configured) will mint or delete DOI entries as appropriate + // while the item is saved in the submission / workflow process + + // This step simply looks for existing identifier data and returns it in section data for rendering + return getIdentifierData(obj); + } + + /** + * Get data about existing identifiers for this in-progress submission item - this method doesn't require + * submissionService or step config, so can be more easily called from doPatchProcessing as well + * + * @param obj The workspace or workflow item + * @return A simple DataIdentifiers bean containing doi, handle and list of other identifiers + */ + private DataIdentifiers getIdentifierData(InProgressSubmission obj) { + log.debug("getIdentifierData() called"); + Context context = getContext(); + DataIdentifiers result = new DataIdentifiers(); + // Load identifier service + IdentifierService identifierService = + IdentifierServiceFactory.getInstance().getIdentifierService(); + // Attempt to look up handle and DOI identifiers for this item + String handle = identifierService.lookup(context, obj.getItem(), Handle.class); + String doi = identifierService.lookup(context, obj.getItem(), DOI.class); + + // Look up all identifiers and if they're not the DOI or handle, add them to the 'other' list + List otherIdentifiers = new ArrayList<>(); + for (String identifier : identifierService.lookup(context, obj.getItem())) { + if (!StringUtils.equals(doi, identifier) && !StringUtils.equals(handle, identifier)) { + otherIdentifiers.add(identifier); + } + } + + // If we got a DOI, format it to its external form + if (StringUtils.isNotEmpty(doi)) { + try { + doi = IdentifierServiceFactory.getInstance().getDOIService().DOIToExternalForm(doi); + } catch (IdentifierException e) { + log.error("Error formatting DOI: " + doi); + } + } + // If we got a handle, format it to its canonical form + if (StringUtils.isNotEmpty(handle)) { + handle = HandleServiceFactory.getInstance().getHandleService().getCanonicalForm(handle); + } + + // Populate bean with data and return + result.setDoi(doi); + result.setHandle(handle); + result.setOtherIdentifiers(otherIdentifiers); + + return result; + } + + /** + * Utility method to get DSpace context from the HTTP request + * @return DSpace context + */ + private Context getContext() { + Context context; + Request currentRequest = DSpaceServicesFactory.getInstance().getRequestService().getCurrentRequest(); + if (currentRequest != null) { + HttpServletRequest request = currentRequest.getHttpServletRequest(); + context = ContextUtil.obtainContext(request); + } else { + context = new Context(); + } + + return context; + } + + /** + * This step is currently just for displaying identifiers and does not take additional patch operations + * @param context + * the DSpace context + * @param currentRequest + * the http request + * @param source + * the in progress submission + * @param op + * the json patch operation + * @param stepConf + * @throws Exception + */ + @Override + public void doPatchProcessing(Context context, HttpServletRequest currentRequest, InProgressSubmission source, + Operation op, SubmissionStepConfig stepConf) throws Exception { + log.warn("Not implemented"); + } + +} diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml index cc23dbc09eca..eca9acf79fd7 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml @@ -162,6 +162,13 @@ submission-form + + + submit.progressbar.identifiers + org.dspace.app.rest.submit.step.ShowIdentifiersStep + identifiers + + Sample @@ -194,6 +201,8 @@ + + diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml new file mode 100644 index 000000000000..206b801d0842 --- /dev/null +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java new file mode 100644 index 000000000000..2d0241b35760 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java @@ -0,0 +1,123 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; +import java.sql.SQLException; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test suite for testing the Show Identifiers submission step + * + * @author Kim Shepherd + * + */ +public class SubmissionShowIdentifiersRestIT extends AbstractControllerIntegrationTest { + + @Autowired + private WorkspaceItemService workspaceItemService; + + @Autowired + private ConfigurationService configurationService; + + private Collection collection; + private EPerson submitter; + + @Override + public void setUp() throws Exception { + super.setUp(); + + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Root community").build(); + + submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter.em@test.com") + .withPassword(password) + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .withEntityType("Publication") + .withSubmitterGroup(submitter).build(); + + // Manually set configuration to allow registration handles, DOIs at workspace item creation + configurationService.setProperty("identifiers.submission.register", true); + + context.restoreAuthSystemState(); + } + + @After + public void after() throws SQLException, IOException, AuthorizeException { + context.turnOffAuthorisationSystem(); + workspaceItemService.findAll(context).forEach(this::deleteWorkspaceItem); + // Manually restore identifiers configuration + configurationService.setProperty("identifiers.submission.register", false); + context.restoreAuthSystemState(); + } + + private void deleteWorkspaceItem(WorkspaceItem workspaceItem) { + try { + workspaceItemService.deleteAll(context, workspaceItem); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(); + } + } + + @Test + public void testItemHandleReservation() throws Exception { + // Test publication that should get Handle and DOI + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = createWorkspaceItem("Test publication", collection); + context.restoreAuthSystemState(); + String submitterToken = getAuthToken(submitter.getEmail(), password); + getClient(submitterToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.identifiers.handle").exists()); + } + + @Test + public void testItemDoiReservation() throws Exception { + // Test publication that should get Handle and DOI + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = createWorkspaceItem("Test publication", collection); + context.restoreAuthSystemState(); + + String submitterToken = getAuthToken(submitter.getEmail(), password); + getClient(submitterToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.identifiers.doi").exists()); + } + + private WorkspaceItem createWorkspaceItem(String title, Collection collection) { + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle(title) + .withSubmitter(submitter) + .build(); + return workspaceItem; + } + +} \ No newline at end of file diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 129ad0ca5646..2a35e894593a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1660,6 +1660,7 @@ include = ${module_dir}/discovery.cfg include = ${module_dir}/doi-curation.cfg include = ${module_dir}/google-analytics.cfg include = ${module_dir}/healthcheck.cfg +include = ${module_dir}/identifiers.cfg include = ${module_dir}/irus-statistics.cfg include = ${module_dir}/oai.cfg include = ${module_dir}/openaire-client.cfg diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 299a5fdfceb9..3ad984ec9757 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -209,6 +209,13 @@ org.dspace.app.rest.submit.step.SherpaPolicyStep sherpaPolicy + + + + submit.progressbar.identifiers + org.dspace.app.rest.submit.step.ShowIdentifiersStep + identifiers + diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg new file mode 100644 index 000000000000..3e0992e090ee --- /dev/null +++ b/dspace/config/modules/identifiers.cfg @@ -0,0 +1,11 @@ +#----------------------------------------------------------------------# +#---------------------IDENTIFIER CONFIGURATIONS------------------------# +#----------------------------------------------------------------------# +# These configs are used for additional identifier configuration such # +# as the Show Identifiers step which can "pre-mint" DOIs and Handles # +#----------------------------------------------------------------------# + +# Should identifiers be registered in the Show Identifiers step and minted / updated / deleted as appropriate +# in the DOI Consumer? Setting this property to 'true' is recommended +# Default: false +#identifiers.submission.register = false \ No newline at end of file From ef6af28fb1b63303fcf95d55113eb0d9432023c4 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 26 Jul 2022 15:18:10 +1200 Subject: [PATCH 261/394] [TLC-249] Remove unused import in WorkspaceItemServiceImpl --- .../main/java/org/dspace/content/WorkspaceItemServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index 32be655dae9a..eb64d8a43d86 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -24,7 +24,6 @@ import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.WorkspaceItemDAO; -import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; From 8fcda7f03a34636572ee16c8093ff84ae5df05b7 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 26 Jul 2022 15:57:26 +1200 Subject: [PATCH 262/394] [TLC-249] Update section count test in SubmissionDefinitionsControllerIT --- .../org/dspace/app/rest/SubmissionDefinitionsControllerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index 5f93411bb991..e7d43ec4d620 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -205,7 +205,7 @@ public void findSections() throws Exception { // We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) // Match only that a section exists with a submission configuration behind - .andExpect(jsonPath("$._embedded.submissionsections", hasSize(8))) + .andExpect(jsonPath("$._embedded.submissionsections", hasSize(9))) .andExpect(jsonPath("$._embedded.submissionsections", Matchers.hasItem( allOf( From 20aeedfdb31560cb2d75074a6da15ed46ecbd1ad Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 15 Aug 2022 16:07:26 +1200 Subject: [PATCH 263/394] [TLC-249] Refactor to support better filter usage, PENDING and MINTED status --- .../content/InstallItemServiceImpl.java | 23 ++- .../content/WorkspaceItemServiceImpl.java | 18 +- .../dspace/content/logic/DefaultFilter.java | 12 ++ .../java/org/dspace/content/logic/Filter.java | 5 +- .../org/dspace/content/logic/FilterUtils.java | 74 ++++++++ .../org/dspace/content/logic/TrueFilter.java | 41 +++++ .../logic/condition/IsArchivedCondition.java | 38 +++++ .../org/dspace/ctask/general/RegisterDOI.java | 10 +- .../identifier/DOIIdentifierProvider.java | 159 ++++++++++-------- .../FilteredIdentifierProvider.java | 40 +++-- .../identifier/IdentifierServiceImpl.java | 97 ++++++++++- .../VersionedDOIIdentifierProvider.java | 35 +++- .../dspace/identifier/doi/DOIConsumer.java | 122 +++++++------- .../dspace/identifier/doi/DOIOrganiser.java | 54 +++--- .../identifier/service/IdentifierService.java | 36 ++++ .../identifier/DOIIdentifierProviderTest.java | 21 +-- .../app/rest/ItemIdentifierController.java | 144 ++++++++++++++++ .../dspace/app/rest/model/IdentifierRest.java | 67 ++++++++ .../app/rest/model/IdentifiersRest.java | 47 ++++++ .../org/dspace/app/rest/model/ItemRest.java | 5 + .../app/rest/model/step/DataIdentifiers.java | 4 + .../rest/repository/ItemRestRepository.java | 45 +++++ .../rest/submit/step/ShowIdentifiersStep.java | 23 ++- dspace/config/item-submission.xml | 2 +- dspace/config/modules/identifiers.cfg | 34 +++- .../config/spring/api/identifier-service.xml | 13 +- dspace/config/spring/api/item-filters.xml | 53 ++++-- 27 files changed, 995 insertions(+), 227 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.java create mode 100644 dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index 11cd4c107c34..a4913d136032 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -9,10 +9,17 @@ import java.io.IOException; import java.sql.SQLException; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; +import org.dspace.content.logic.TrueFilter; import org.dspace.content.service.CollectionService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; @@ -20,8 +27,12 @@ import org.dspace.core.Context; import org.dspace.embargo.service.EmbargoService; import org.dspace.event.Event; +import org.dspace.identifier.DOI; +import org.dspace.identifier.Handle; +import org.dspace.identifier.Identifier; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.IdentifierService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -42,9 +53,11 @@ public class InstallItemServiceImpl implements InstallItemService { protected IdentifierService identifierService; @Autowired(required = true) protected ItemService itemService; + @Autowired(required = false) - protected InstallItemServiceImpl() { + Logger log = LogManager.getLogger(InstallItemServiceImpl.class); + protected InstallItemServiceImpl() { } @Override @@ -59,11 +72,17 @@ public Item installItem(Context c, InProgressSubmission is, AuthorizeException { Item item = is.getItem(); Collection collection = is.getCollection(); + // Get map of filters to use for identifier types + Map, Filter> filters = FilterUtils.getIdentifierFilters("install"); try { if (suppliedHandle == null) { - identifierService.register(c, item); + // Register with the filters we've set up + identifierService.register(c, item, filters); } else { + // This will register the handle but a pending DOI won't be compatible and so won't be registered identifierService.register(c, item, suppliedHandle); + // Continue to register just a DOI + identifierService.register(c, item, DOI.class, filters.get(DOI.class)); } } catch (IdentifierException e) { throw new RuntimeException("Can't create an Identifier!", e); diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index eb64d8a43d86..6bcf65106d5f 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -24,6 +24,8 @@ import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.WorkspaceItemDAO; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; import org.dspace.content.service.CollectionService; import org.dspace.content.service.ItemService; import org.dspace.content.service.WorkspaceItemService; @@ -32,8 +34,12 @@ import org.dspace.core.LogHelper; import org.dspace.eperson.EPerson; import org.dspace.event.Event; +import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.identifier.Identifier; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.DOIService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowService; @@ -61,6 +67,8 @@ public class WorkspaceItemServiceImpl implements WorkspaceItemService { protected ItemService itemService; @Autowired(required = true) protected WorkflowService workflowService; + @Autowired(required = true) + protected DOIService doiService; protected WorkspaceItemServiceImpl() { @@ -169,7 +177,15 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b if (DSpaceServicesFactory.getInstance().getConfigurationService() .getBooleanProperty("identifiers.submission.register", false)) { try { - IdentifierServiceFactory.getInstance().getIdentifierService().register(context, item); + // Get map of filters to use for identifier types + Map, Filter> filters = FilterUtils.getIdentifierFilters("workspace"); + IdentifierServiceFactory.getInstance().getIdentifierService().register(context, item, filters); + // Look for a DOI and move it to PENDING + DOI doi = doiService.findDOIByDSpaceObject(context, item); + if (doi != null) { + doi.setStatus(DOIIdentifierProvider.PENDING); + doiService.update(context, doi); + } } catch (IdentifierException e) { log.error("Could not register identifier(s) for item {}: {}", item.getID(), e.getMessage()); } diff --git a/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java b/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java index 490c3949ea3a..936d12987ff6 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java @@ -22,6 +22,7 @@ */ public class DefaultFilter implements Filter { private LogicalStatement statement; + private String name; private final static Logger log = LogManager.getLogger(); /** @@ -44,4 +45,15 @@ public void setStatement(LogicalStatement statement) { public boolean getResult(Context context, Item item) throws LogicalStatementException { return this.statement.getResult(context, item); } + + @Override + public void setBeanName(String name) { + log.debug("Initialize bean " + name); + this.name = name; + } + + @Override + public String getName() { + return name; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/logic/Filter.java b/dspace-api/src/main/java/org/dspace/content/logic/Filter.java index 84e9d6bc0880..97ab73ddfa88 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/Filter.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/Filter.java @@ -9,6 +9,7 @@ import org.dspace.content.Item; import org.dspace.core.Context; +import org.springframework.beans.factory.BeanNameAware; /** * The interface for Filter currently doesn't add anything to LogicalStatement but inherits from it @@ -22,7 +23,7 @@ * @author Kim Shepherd * @see org.dspace.content.logic.DefaultFilter */ -public interface Filter extends LogicalStatement { +public interface Filter extends LogicalStatement, BeanNameAware { /** * Get the result of logical evaluation for an item * @param context DSpace context @@ -32,4 +33,6 @@ public interface Filter extends LogicalStatement { */ @Override boolean getResult(Context context, Item item) throws LogicalStatementException; + + String getName(); } diff --git a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java new file mode 100644 index 000000000000..006d45de28a5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.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.content.logic; + +import java.util.HashMap; +import java.util.Map; + +import org.dspace.identifier.DOI; +import org.dspace.identifier.Handle; +import org.dspace.identifier.Identifier; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * General utility methods for logical item filtering + * + * @author Kim Shepherd + */ +public class FilterUtils { + + @Autowired(required = true) + ConfigurationService configurationService; + + /** + * Get a Filter by name + * @param property DSpace configuration property name (Apache Commons config) + * @return Filter or null + */ + public static Filter getFilterFromConfiguration(String property) { + String filterName = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty(property); + if (filterName != null) { + return DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(filterName, Filter.class); + } + return null; + } + + /** + * Get a Filter by name + * @param property DSpace configuration property name (Apache Commons config) + * @return Filter or null + */ + public static Filter getFilterFromConfiguration(String property, Filter defaultFilter) { + Filter filter = getFilterFromConfiguration(property); + if (filter != null) { + return filter; + } + return defaultFilter; + } + + /** + * Get a map of identifier types and filters to use when creating workspace or archived items + * @return + */ + public static Map, Filter> getIdentifierFilters(String status) { + if (status == null) { + status = "install"; + } + Map, Filter> filters = new HashMap<>(); + // Put DOI 'can we create DOI on install / workspace?' filter + Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter." + status); + // A null filter should be handled safely by the identifier provier (default, or "always true") + filters.put(DOI.class, filter); + // This won't have an affect until handle providers implement filtering, but is an example of + // how the filters can be used for other types + filters.put(Handle.class, new TrueFilter()); + return filters; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.java b/dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.java new file mode 100644 index 000000000000..b15ab4eaaafe --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/TrueFilter.java @@ -0,0 +1,41 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.logic; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * Extremely simple filter that always returns true! + * Useful to pass to methods that expect a filter, in order to effectively say "all items". + * This could be configured in Spring XML but it is more stable and reliable to have it hard-coded here + * so that any broken configuration doesn't silently break parts of DSpace that expect it to work. + * + * @author Kim Shepherd + */ +public class TrueFilter implements Filter { + private String name; + private final static Logger log = LogManager.getLogger(); + + public boolean getResult(Context context, Item item) throws LogicalStatementException { + return true; + } + + @Override + public void setBeanName(String name) { + log.debug("Initialize bean " + name); + this.name = name; + } + + @Override + public String getName() { + return name; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java new file mode 100644 index 000000000000..4ae1c7dc9139 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.logic.condition; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.content.Item; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.core.Context; + +/** + * A condition that returns true if the item is withdrawn + * + * @author Kim Shepherd + * @version $Revision$ + */ +public class IsArchivedCondition extends AbstractCondition { + private final static Logger log = LogManager.getLogger(); + + /** + * Return true if item is withdrawn + * Return false if not + * @param context DSpace context + * @param item Item to evaluate + * @return boolean result of evaluation + * @throws LogicalStatementException + */ + @Override + public boolean getResult(Context context, Item item) throws LogicalStatementException { + log.debug("Result of isWithdrawn is " + item.isArchived()); + return item.isArchived(); + } +} diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java index 4e777d70a8b4..8cc5f85d0562 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java @@ -13,6 +13,9 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; +import org.dspace.content.logic.TrueFilter; import org.dspace.curate.AbstractCurationTask; import org.dspace.curate.Curator; import org.dspace.identifier.DOIIdentifierProvider; @@ -46,8 +49,6 @@ public class RegisterDOI extends AbstractCurationTask { @Override public void init(Curator curator, String taskId) throws IOException { super.init(curator, taskId); - // Get 'skip filter' behaviour from configuration, with a default value of 'true' - skipFilter = configurationService.getBooleanProperty(PLUGIN_PREFIX + ".skip-filter", true); // Get distribution behaviour from configuration, with a default value of 'false' distributed = configurationService.getBooleanProperty(PLUGIN_PREFIX + ".distributed", false); log.debug("PLUGIN_PREFIX = " + PLUGIN_PREFIX + ", skipFilter = " + skipFilter + @@ -118,8 +119,9 @@ private String register(Item item) { String doi = null; // Attempt DOI registration and report successes and failures try { - log.debug("Registering DOI with skipFilter = " + skipFilter); - doi = provider.register(Curator.curationContext(), item, skipFilter); + Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter.curation", + new TrueFilter()); + doi = provider.register(Curator.curationContext(), item, filter); if (doi != null) { String message = "New DOI minted in database for item " + item.getHandle() + ": " + doi + ". This DOI will be registered online with the DOI provider when the queue is next run"; diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java index 66e7b94a4bd4..f5008d575e25 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -21,6 +21,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.logic.Filter; import org.dspace.content.logic.LogicalStatementException; +import org.dspace.content.logic.TrueFilter; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -44,6 +45,7 @@ *

Any identifier a method of this class returns is a string in the following format: doi:10.123/456.

* * @author Pascal-Nicolas Becker + * @author Kim Shepherd */ public class DOIIdentifierProvider extends FilteredIdentifierProvider { private static final Logger log = LoggerFactory.getLogger(DOIIdentifierProvider.class); @@ -71,16 +73,29 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { public static final String MD_SCHEMA = "dc"; public static final String DOI_ELEMENT = "identifier"; public static final String DOI_QUALIFIER = "uri"; - + // The DOI is queued for registered with the service provider public static final Integer TO_BE_REGISTERED = 1; + // The DOI is queued for reservation with the service provider public static final Integer TO_BE_RESERVED = 2; + // The DOI has been registered online public static final Integer IS_REGISTERED = 3; + // The DOI has been reserved online public static final Integer IS_RESERVED = 4; + // The DOI is reserved and requires an updated metadata record to be sent to the service provider public static final Integer UPDATE_RESERVED = 5; + // The DOI is registered and requires an updated metadata record to be sent to the service provider public static final Integer UPDATE_REGISTERED = 6; + // The DOI metadata record should be updated before performing online registration public static final Integer UPDATE_BEFORE_REGISTRATION = 7; + // The DOI will be deleted locally and marked as deleted in the DOI service provider public static final Integer TO_BE_DELETED = 8; + // The DOI has been deleted and is no longer associated with an item public static final Integer DELETED = 9; + // The DOI is created in the database and is waiting for either successful filter check on item install or + // manual intervention by an administrator to proceed to reservation or registration + public static final Integer PENDING = 10; + // The DOI is created in the database, but no more context is known + public static final Integer MINTED = 11; @Autowired(required = true) protected DOIService doiService; @@ -89,8 +104,6 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { @Autowired(required = true) protected ItemService itemService; - protected Filter filterService; - /** * Empty / default constructor for Spring */ @@ -153,16 +166,6 @@ public void setDOIConnector(DOIConnector connector) { this.connector = connector; } - /** - * Set the Filter to use when testing items to see if a DOI should be registered - * Spring will use this setter to set the filter from the configured property in identifier-services.xml - * @param filterService - an object implementing the org.dspace.content.logic.Filter interface - */ - @Override - public void setFilterService(Filter filterService) { - this.filterService = filterService; - } - /** * This identifier provider supports identifiers of type * {@link org.dspace.identifier.DOI}. @@ -206,7 +209,7 @@ public boolean supports(String identifier) { @Override public String register(Context context, DSpaceObject dso) throws IdentifierException { - return register(context, dso, false); + return register(context, dso, this.filter); } /** @@ -219,29 +222,29 @@ public String register(Context context, DSpaceObject dso) @Override public void register(Context context, DSpaceObject dso, String identifier) throws IdentifierException { - register(context, dso, identifier, false); + register(context, dso, identifier, this.filter); } /** * Register a new DOI for a given DSpaceObject * @param context - DSpace context * @param dso - DSpaceObject identified by the new DOI - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration + * @param filter - Logical item filter to determine whether this identifier should be registered * @throws IdentifierException */ @Override - public String register(Context context, DSpaceObject dso, boolean skipFilter) + public String register(Context context, DSpaceObject dso, Filter filter) throws IdentifierException { if (!(dso instanceof Item)) { // DOI are currently assigned only to Item return null; } - String doi = mint(context, dso, skipFilter); + String doi = mint(context, dso, filter); // register tries to reserve doi if it's not already. // So we don't have to reserve it here. - register(context, dso, doi, skipFilter); + register(context, dso, doi, filter); return doi; } @@ -250,11 +253,11 @@ public String register(Context context, DSpaceObject dso, boolean skipFilter) * @param context - DSpace context * @param dso - DSpaceObject identified by the new DOI * @param identifier - String containing the DOI to register - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration + * @param filter - Logical item filter to determine whether this identifier should be registered * @throws IdentifierException */ @Override - public void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public void register(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException { if (!(dso instanceof Item)) { // DOI are currently assigned only to Item @@ -265,7 +268,7 @@ public void register(Context context, DSpaceObject dso, String identifier, boole // search DOI in our db try { - doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + doiRow = loadOrCreateDOI(context, dso, doi, filter); } catch (SQLException ex) { log.error("Error in databse connection: " + ex.getMessage()); throw new RuntimeException("Error in database conncetion.", ex); @@ -277,7 +280,6 @@ public void register(Context context, DSpaceObject dso, String identifier, boole + "is marked as DELETED.", DOIIdentifierException.DOI_IS_DELETED); } - // Check status of DOI if (IS_REGISTERED.equals(doiRow.getStatus())) { return; } @@ -290,6 +292,7 @@ public void register(Context context, DSpaceObject dso, String identifier, boole log.warn("SQLException while changing status of DOI {} to be registered.", doi); throw new RuntimeException(sqle); } + } /** @@ -309,7 +312,7 @@ public void register(Context context, DSpaceObject dso, String identifier, boole @Override public void reserve(Context context, DSpaceObject dso, String identifier) throws IdentifierException, IllegalArgumentException { - reserve(context, dso, identifier, false); + reserve(context, dso, identifier, this.filter); } /** @@ -317,20 +320,18 @@ public void reserve(Context context, DSpaceObject dso, String identifier) * @param context - DSpace context * @param dso - DSpaceObject identified by this DOI * @param identifier - String containing the DOI to reserve - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing reservation + * @param filter - Logical item filter to determine whether this identifier should be reserved * @throws IdentifierException * @throws IllegalArgumentException */ @Override - public void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public void reserve(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException, IllegalArgumentException { String doi = doiService.formatIdentifier(identifier); DOI doiRow = null; try { - // if the doi is in our db already loadOrCreateDOI just returns. - // if it is not loadOrCreateDOI safes the doi. - doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + doiRow = loadOrCreateDOI(context, dso, doi, filter); } catch (SQLException sqle) { throw new RuntimeException(sqle); } @@ -359,7 +360,7 @@ public void reserve(Context context, DSpaceObject dso, String identifier, boolea */ public void reserveOnline(Context context, DSpaceObject dso, String identifier) throws IdentifierException, IllegalArgumentException, SQLException { - reserveOnline(context, dso, identifier, false); + reserveOnline(context, dso, identifier, this.filter); } /** @@ -367,16 +368,16 @@ public void reserveOnline(Context context, DSpaceObject dso, String identifier) * @param context - DSpace context * @param dso - DSpaceObject identified by this DOI * @param identifier - String containing the DOI to reserve - * @param skipFilter - skip the filters for {@link checkMintable(Context, DSpaceObject)} + * @param filter - Logical item filter to determine whether this identifier should be reserved online * @throws IdentifierException * @throws IllegalArgumentException * @throws SQLException */ - public void reserveOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public void reserveOnline(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException, IllegalArgumentException, SQLException { String doi = doiService.formatIdentifier(identifier); // get TableRow and ensure DOI belongs to dso regarding our db - DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + DOI doiRow = loadOrCreateDOI(context, dso, doi, filter); if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to reserve a DOI that " @@ -402,7 +403,7 @@ public void reserveOnline(Context context, DSpaceObject dso, String identifier, public void registerOnline(Context context, DSpaceObject dso, String identifier) throws IdentifierException, IllegalArgumentException, SQLException { - registerOnline(context, dso, identifier, false); + registerOnline(context, dso, identifier, this.filter); } @@ -411,18 +412,17 @@ public void registerOnline(Context context, DSpaceObject dso, String identifier) * @param context - DSpace context * @param dso - DSpaceObject identified by this DOI * @param identifier - String containing the DOI to register - * @param skipFilter - skip filters for {@link checkMintable(Context, DSpaceObject)} + * @param filter - Logical item filter to determine whether this identifier should be registered online * @throws IdentifierException * @throws IllegalArgumentException * @throws SQLException */ - public void registerOnline(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public void registerOnline(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException, IllegalArgumentException, SQLException { - log.debug("registerOnline: skipFilter is " + skipFilter); String doi = doiService.formatIdentifier(identifier); // get TableRow and ensure DOI belongs to dso regarding our db - DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + DOI doiRow = loadOrCreateDOI(context, dso, doi, filter); if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to register a DOI that " @@ -435,7 +435,7 @@ public void registerOnline(Context context, DSpaceObject dso, String identifier, } catch (DOIIdentifierException die) { // do we have to reserve DOI before we can register it? if (die.getCode() == DOIIdentifierException.RESERVE_FIRST) { - this.reserveOnline(context, dso, identifier, skipFilter); + this.reserveOnline(context, dso, identifier, filter); connector.registerDOI(context, dso, doi); } else { throw die; @@ -471,17 +471,22 @@ public void updateMetadata(Context context, DSpaceObject dso, String identifier) throws IdentifierException, IllegalArgumentException, SQLException { String doi = doiService.formatIdentifier(identifier); - - boolean skipFilter = false; + // Use the default filter unless we find the object + Filter updateFilter = this.filter; if (doiService.findDOIByDSpaceObject(context, dso) != null) { // We can skip the filter here since we know the DOI already exists for the item log.debug("updateMetadata: found DOIByDSpaceObject: " + doiService.findDOIByDSpaceObject(context, dso).getDoi()); - skipFilter = true; + updateFilter = new TrueFilter(); } - DOI doiRow = loadOrCreateDOI(context, dso, doi, skipFilter); + DOI doiRow = loadOrCreateDOI(context, dso, doi, updateFilter); + + if (PENDING.equals(doiRow.getStatus())) { + log.info("Not updating metadata for PENDING doi: " + doi); + return; + } if (DELETED.equals(doiRow.getStatus()) || TO_BE_DELETED.equals(doiRow.getStatus())) { throw new DOIIdentifierException("You tried to register a DOI that " @@ -571,19 +576,19 @@ public void updateMetadataOnline(Context context, DSpaceObject dso, String ident @Override public String mint(Context context, DSpaceObject dso) throws IdentifierException { - return mint(context, dso, false); + return mint(context, dso, this.filter); } /** * Mint a new DOI in DSpace - this is usually the first step of registration * @param context - DSpace context * @param dso - DSpaceObject identified by the new identifier - * @param skipFilter - boolean indicating whether to skip any filtering of items before minting. + * @param filter - Logical item filter to determine whether this identifier should be registered * @return a String containing the new identifier * @throws IdentifierException */ @Override - public String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException { + public String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException { String doi = null; try { @@ -597,7 +602,7 @@ public String mint(Context context, DSpaceObject dso, boolean skipFilter) throws } if (null == doi) { try { - DOI doiRow = loadOrCreateDOI(context, dso, null, skipFilter); + DOI doiRow = loadOrCreateDOI(context, dso, null, filter); doi = DOI.SCHEME + doiRow.getDoi(); } catch (SQLException e) { @@ -895,7 +900,7 @@ public String getDOIByObject(Context context, DSpaceObject dso) throws SQLExcept */ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier) throws SQLException, DOIIdentifierException, IdentifierNotApplicableException { - return loadOrCreateDOI(context, dso, doiIdentifier, false); + return loadOrCreateDOI(context, dso, doiIdentifier, this.filter); } /** @@ -910,13 +915,13 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent * @param context - DSpace context * @param dso - DSpaceObject to identify * @param doiIdentifier - DOI to load or create (null to mint a new one) - * @param skipFilter - Whether or not to skip the filters for the checkMintable() check + * @param filter - Logical item filter to determine whether this identifier should be registered * @return * @throws SQLException * @throws DOIIdentifierException * @throws org.dspace.identifier.IdentifierNotApplicableException passed through. */ - protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, boolean skipFilter) + protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdentifier, Filter filter) throws SQLException, DOIIdentifierException, IdentifierNotApplicableException { DOI doi = null; @@ -954,6 +959,8 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent // doi is assigned to a DSO; is it assigned to our specific dso? // check if DOI already belongs to dso if (dso.getID().equals(doi.getDSpaceObject().getID())) { + // Before we return this, check the filter + checkMintable(context, filter, dso); return doi; } else { throw new DOIIdentifierException("Trying to create a DOI " + @@ -963,15 +970,8 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent } } - // we did not find the doi in the database or shall reassign it. Before doing so, we should check if a - // filter is in place to prevent the creation of new DOIs for certain items. - if (skipFilter) { - log.warn("loadOrCreateDOI: Skipping default item filter"); - } else { - // Find out if we're allowed to create a DOI - // throws an exception if creation of a new DOI is prohibited by a filter - checkMintable(context, dso); - } + // Check if this item is eligible for minting. An IdentifierNotApplicableException will be thrown if not. + checkMintable(context, filter, dso); // check prefix if (!doiIdentifier.startsWith(this.getPrefix() + "/")) { @@ -984,15 +984,8 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent doi = doiService.create(context); } } else { - // We need to generate a new DOI. Before doing so, we should check if a - // filter is in place to prevent the creation of new DOIs for certain items. - if (skipFilter) { - log.warn("loadOrCreateDOI: Skipping default item filter"); - } else { - // Find out if we're allowed to create a DOI - // throws an exception if creation of a new DOI is prohibited by a filter - checkMintable(context, dso); - } + // Check if this item is eligible for minting. An IdentifierNotApplicableException will be thrown if not. + checkMintable(context, filter, dso); doi = doiService.create(context); doiIdentifier = this.getPrefix() + "/" + this.getNamespaceSeparator() + @@ -1002,7 +995,7 @@ protected DOI loadOrCreateDOI(Context context, DSpaceObject dso, String doiIdent // prepare new doiRow doi.setDoi(doiIdentifier); doi.setDSpaceObject(dso); - doi.setStatus(null); + doi.setStatus(MINTED); try { doiService.update(context, doi); } catch (SQLException e) { @@ -1102,20 +1095,26 @@ protected void removeDOIFromObject(Context context, DSpaceObject dso, String doi /** * Checks to see if an item can have a DOI minted, using the configured logical filter * @param context + * @param filter Logical item filter to apply * @param dso The item to be evaluated * @throws DOIIdentifierNotApplicableException */ @Override - public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException { + public void checkMintable(Context context, Filter filter, DSpaceObject dso) + throws DOIIdentifierNotApplicableException { + if (filter == null) { + // If a null filter was passed and we have a good default filter to apply, apply it. + // Otherwise set to TrueFilter which means "no filtering" + filter = Objects.requireNonNullElseGet(this.filter, TrueFilter::new); + } // If the check fails, an exception will be thrown to be caught by the calling method - if (this.filterService != null && contentServiceFactory - .getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) { + if (contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) { try { - boolean result = filterService.getResult(context, (Item) dso); + boolean result = filter.getResult(context, (Item) dso); log.debug("Result of filter for " + dso.getHandle() + " is " + result); if (!result) { throw new DOIIdentifierNotApplicableException("Item " + dso.getHandle() + - " was evaluated as 'false' by the item filter, not minting"); + " was evaluated as 'false' by the item filter, not minting"); } } catch (LogicalStatementException e) { log.error("Error evaluating item with logical filter: " + e.getLocalizedMessage()); @@ -1125,4 +1124,16 @@ public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifie log.debug("DOI Identifier Provider: filterService is null (ie. don't prevent DOI minting)"); } } + + /** + * Checks to see if an item can have a DOI minted, using the configured logical filter + * @param context + * @param dso The item to be evaluated + * @throws DOIIdentifierNotApplicableException + */ + @Override + public void checkMintable(Context context, DSpaceObject dso) throws DOIIdentifierNotApplicableException { + checkMintable(context, this.filter, dso); + } + } \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java index e5f222ff29c4..440ee3100fa7 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java @@ -12,8 +12,8 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.logic.Filter; +import org.dspace.content.logic.TrueFilter; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; /** * This abstract class adds extra method signatures so that implementing IdentifierProviders can @@ -24,26 +24,28 @@ */ public abstract class FilteredIdentifierProvider extends IdentifierProvider { - protected Filter filterService; + protected Filter filter = new TrueFilter(); /** - * Setter for spring to set the filter service from the property in configuration XML - * @param filterService - an object implementing the org.dspace.content.logic.Filter interface + * Setter for spring to set the default filter from the property in configuration XML + * @param filter - an object implementing the org.dspace.content.logic.Filter interface */ - @Autowired - public void setFilterService(Filter filterService) { - this.filterService = filterService; + public void setFilter(Filter filter) { + this.filter = filter; + if (this.filter == null) { + this.filter = new TrueFilter(); + } } /** * Register a new identifier for a given DSpaceObject * @param context - DSpace context * @param dso - DSpaceObject to use for identifier registration - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration + * @param filter - Logical item filter to determine whether this identifier should be registered * @return identifier * @throws IdentifierException */ - public abstract String register(Context context, DSpaceObject dso, boolean skipFilter) + public abstract String register(Context context, DSpaceObject dso, Filter filter) throws IdentifierException; /** @@ -51,10 +53,10 @@ public abstract String register(Context context, DSpaceObject dso, boolean skipF * @param context - DSpace context * @param dso - DSpaceObject identified by the new identifier * @param identifier - String containing the identifier to register - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing registration + * @param filter - Logical item filter to determine whether this identifier should be registered * @throws IdentifierException */ - public abstract void register(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public abstract void register(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException; /** @@ -62,23 +64,23 @@ public abstract void register(Context context, DSpaceObject dso, String identifi * @param context - DSpace context * @param dso - DSpaceObject identified by this identifier * @param identifier - String containing the identifier to reserve - * @param skipFilter - boolean indicating whether to skip any filtering of items before performing reservation + * @param filter - Logical item filter to determine whether this identifier should be reserved * @throws IdentifierException * @throws IllegalArgumentException * @throws SQLException */ - public abstract void reserve(Context context, DSpaceObject dso, String identifier, boolean skipFilter) + public abstract void reserve(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException, IllegalArgumentException, SQLException; /** * Mint a new identifier in DSpace - this is usually the first step of registration * @param context - DSpace context * @param dso - DSpaceObject identified by the new identifier - * @param skipFilter - boolean indicating whether to skip any filtering of items before minting. + * @param filter - Logical item filter to determine whether this identifier should be registered * @return a String containing the new identifier * @throws IdentifierException */ - public abstract String mint(Context context, DSpaceObject dso, boolean skipFilter) throws IdentifierException; + public abstract String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException; /** * Check configured item filters to see if this identifier is allowed to be minted @@ -88,5 +90,13 @@ public abstract void reserve(Context context, DSpaceObject dso, String identifie */ public abstract void checkMintable(Context context, DSpaceObject dso) throws IdentifierException; + /** + * Check configured item filters to see if this identifier is allowed to be minted + * @param context - DSpace context + * @param filter - Logical item filter + * @param dso - DSpaceObject to be inspected + * @throws IdentifierException + */ + public abstract void checkMintable(Context context, Filter filter, DSpaceObject dso) throws IdentifierException; } diff --git a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java index d0b6e4417e04..2d41be0daa6a 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -17,6 +18,8 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.TrueFilter; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; import org.dspace.identifier.service.IdentifierService; @@ -98,7 +101,7 @@ public void reserve(Context context, DSpaceObject dso, String identifier) @Override public void register(Context context, DSpaceObject dso) - throws AuthorizeException, SQLException, IdentifierException { + throws AuthorizeException, SQLException, IdentifierException { //We need to commit our context because one of the providers might require the handle created above // Next resolve all other services for (IdentifierProvider service : providers) { @@ -112,6 +115,98 @@ public void register(Context context, DSpaceObject dso) contentServiceFactory.getDSpaceObjectService(dso).update(context, dso); } + @Override + public void register(Context context, DSpaceObject dso, Class type, Filter filter) + throws AuthorizeException, SQLException, IdentifierException { + //We need to commit our context because one of the providers might require the handle created above + // Next resolve all other services + boolean registered = false; + for (IdentifierProvider service : providers) { + if (service.supports(type)) { + try { + if (service instanceof FilteredIdentifierProvider) { + FilteredIdentifierProvider filteredService = (FilteredIdentifierProvider)service; + filteredService.register(context, dso, filter); + } else { + service.register(context, dso); + } + registered = true; + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not registered (inapplicable): " + e.getMessage()); + } + } + } + if (!registered) { + throw new IdentifierException("Cannot register identifier: Didn't " + + "find a provider that supports this identifier."); + } + //Update our item / collection / community + contentServiceFactory.getDSpaceObjectService(dso).update(context, dso); + } + + @Override + public void register(Context context, DSpaceObject dso, Class type) + throws AuthorizeException, SQLException, IdentifierException { + //We need to commit our context because one of the providers might require the handle created above + // Next resolve all other services + boolean registered = false; + for (IdentifierProvider service : providers) { + if (service.supports(type)) { + try { + service.register(context, dso); + registered = true; + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not registered (inapplicable): " + e.getMessage()); + } + } + } + if (!registered) { + throw new IdentifierException("Cannot register identifier: Didn't " + + "find a provider that supports this identifier."); + } + //Update our item / collection / community + contentServiceFactory.getDSpaceObjectService(dso).update(context, dso); + } + + @Override + public void register(Context context, DSpaceObject dso, Map, Filter> typeFilters) + throws AuthorizeException, SQLException, IdentifierException { + //We need to commit our context because one of the providers might require the handle created above + // Next resolve all other services + for (IdentifierProvider service : providers) { + try { + // If the service supports filtering, look through the map and the first supported class + // we find, set the filter and break. If no filter was seen for this type, just let the provider + // use its own implementation. + if (service instanceof FilteredIdentifierProvider) { + FilteredIdentifierProvider filteredService = (FilteredIdentifierProvider)service; + Filter filter = null; + for (Class type : typeFilters.keySet()) { + if (service.supports(type)) { + filter = typeFilters.get(type); + break; + } + } + if (filter != null) { + // Pass the found filter to the provider + filteredService.register(context, dso, filter); + } else { + // Let the provider use the default filter / behaviour + filteredService.register(context, dso); + } + } else { + service.register(context, dso); + } + } catch (IdentifierNotApplicableException e) { + log.warn("Identifier not registered (inapplicable): " + e.getMessage()); + } + } + //Update our item / collection / community + contentServiceFactory.getDSpaceObjectService(dso).update(context, dso); + } + + + @Override public void register(Context context, DSpaceObject object, String identifier) throws AuthorizeException, SQLException, IdentifierException { diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java index a864b4be4bf2..e7c786d5f8ce 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedDOIIdentifierProvider.java @@ -18,6 +18,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataValue; +import org.dspace.content.logic.Filter; import org.dspace.core.Context; import org.dspace.identifier.doi.DOIConnector; import org.dspace.identifier.doi.DOIIdentifierException; @@ -49,7 +50,12 @@ public class VersionedDOIIdentifierProvider extends DOIIdentifierProvider { protected VersionHistoryService versionHistoryService; @Override - public String mint(Context context, DSpaceObject dso) + public String mint(Context context, DSpaceObject dso) throws IdentifierException { + return mint(context, dso, this.filter); + } + + @Override + public String mint(Context context, DSpaceObject dso, Filter filter) throws IdentifierException { if (!(dso instanceof Item)) { throw new IdentifierException("Currently only Items are supported for DOIs."); @@ -79,6 +85,9 @@ public String mint(Context context, DSpaceObject dso) + " with ID " + dso.getID() + ".", ex); } + // Make a call to the filter here to throw an exception instead of carrying on with removal + creation + checkMintable(context, filter, dso); + // check whether we have a DOI in the metadata and if we have to remove it String metadataDOI = getDOIOutOfObject(dso); if (metadataDOI != null) { @@ -111,7 +120,7 @@ public String mint(Context context, DSpaceObject dso) // ensure DOI exists in our database as well and return. // this also checks that the doi is not assigned to another dso already. try { - loadOrCreateDOI(context, dso, versionedDOI); + loadOrCreateDOI(context, dso, versionedDOI, filter); } catch (SQLException ex) { log.error( "A problem with the database connection occurd while processing DOI " + versionedDOI + ".", ex); @@ -127,7 +136,7 @@ public String mint(Context context, DSpaceObject dso) // if we have a history, we have a item doi = makeIdentifierBasedOnHistory(context, dso, history); } else { - doi = loadOrCreateDOI(context, dso, null).getDoi(); + doi = loadOrCreateDOI(context, dso, null, filter).getDoi(); } } catch (SQLException ex) { log.error("SQLException while creating a new DOI: ", ex); @@ -140,7 +149,12 @@ public String mint(Context context, DSpaceObject dso) } @Override - public void register(Context context, DSpaceObject dso, String identifier) + public void register(Context context, DSpaceObject dso, String identifier) throws IdentifierException { + register(context, dso, identifier, this.filter); + } + + @Override + public void register(Context context, DSpaceObject dso, String identifier, Filter filter) throws IdentifierException { if (!(dso instanceof Item)) { throw new IdentifierException("Currently only Items are supported for DOIs."); @@ -220,8 +234,14 @@ protected String getDOIPostfix(String identifier) return doiPostfix; } - // Should never return null! protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history) + throws AuthorizeException, SQLException, DOIIdentifierException, IdentifierNotApplicableException { + return makeIdentifierBasedOnHistory(context, dso, history, this.filter); + } + + // Should never return null! + protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, VersionHistory history, + Filter filter) throws AuthorizeException, SQLException, DOIIdentifierException, IdentifierNotApplicableException { // Mint foreach new version an identifier like: 12345/100.versionNumber // use the bare handle (g.e. 12345/100) for the first version. @@ -244,6 +264,9 @@ protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, } if (previousVersionDOI == null) { + // Before continuing with any new DOI creation, apply the filter + checkMintable(context, filter, dso); + // We need to generate a new DOI. DOI doi = doiService.create(context); @@ -269,7 +292,7 @@ protected String makeIdentifierBasedOnHistory(Context context, DSpaceObject dso, String.valueOf(versionHistoryService.getVersion(context, history, item).getVersionNumber())); } - loadOrCreateDOI(context, dso, identifier); + loadOrCreateDOI(context, dso, identifier, filter); return identifier; } diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java index 622b546defde..1961ce82744c 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIConsumer.java @@ -7,23 +7,32 @@ */ package org.dspace.identifier.doi; +import java.sql.SQLException; + import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.event.Consumer; import org.dspace.event.Event; +import org.dspace.identifier.DOI; import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; -import org.dspace.identifier.IdentifierNotFoundException; +import org.dspace.identifier.IdentifierNotApplicableException; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.DOIService; +import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; import org.dspace.workflow.factory.WorkflowServiceFactory; /** * @author Pascal-Nicolas Becker (p dot becker at tu hyphen berlin dot de) + * @author Kim Shepherd */ public class DOIConsumer implements Consumer { /** @@ -31,12 +40,15 @@ public class DOIConsumer implements Consumer { */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DOIConsumer.class); + ConfigurationService configurationService; + @Override public void initialize() throws Exception { // nothing to do // we can ask spring to give as a properly setuped instance of // DOIIdentifierProvider. Doing so we don't have to configure it and // can load it in consume method as this is not very expensive. + configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); } @@ -63,77 +75,73 @@ public void consume(Context ctx, Event event) throws Exception { return; } Item item = (Item) dso; + DOIIdentifierProvider provider = new DSpace().getSingletonService(DOIIdentifierProvider.class); + boolean inProgress = (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) + != null || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null); + boolean identifiersInSubmission = configurationService.getBooleanProperty("identifiers.submission.register", + false); + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + Filter workspaceFilter = null; + if (identifiersInSubmission) { + workspaceFilter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter.workspace"); + } - /* - if (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) != null - || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null) { - // ignore workflow and workspace items, DOI will be minted when item is installed + if (inProgress && !identifiersInSubmission) { + // ignore workflow and workspace items, DOI will be minted and updated when item is installed + // UNLESS special pending filter is set return; } - */ - DOIIdentifierProvider provider = new DSpace().getSingletonService( - DOIIdentifierProvider.class); - - String doi = null; + DOI doi = null; try { - doi = provider.lookup(ctx, dso); - } catch (IdentifierNotFoundException ex) { + doi = doiService.findDOIByDSpaceObject(ctx, dso); + } catch (SQLException ex) { // nothing to do here, next if clause will stop us from processing // items without dois. } if (doi == null) { - // if the item is workflow or workspace, there is a special case here - the ShowIdentifersStep - // needs this consumer to reserve DOIs in the event that the item now meets criteria for a pre-mint - if (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) != null - || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null) { - if (DSpaceServicesFactory.getInstance().getConfigurationService() - .getBooleanProperty("identifiers.submission.register", false)) { - try { - String newDoi = provider.mint(ctx, item); - if (newDoi != null) { - provider.reserve(ctx, item, newDoi); - log.debug("Consumer minting and reserving new DOI for in-progress item: " + newDoi); - } else { - return; - } - } catch (DOIIdentifierNotApplicableException e) { - log.debug("Consumer skipping mint for item as it was filtered out: " + item.getID()); - } - } else { - log.debug("Consumer skipping mint for item as it is in-progress and" + - "identifiers.submission.register=false: " + item.getID()); + // No DOI. The only time something should be minted is if we have enabled submission reg'n and + // it passes the workspace filter. We also need to update status to PENDING straight after. + if (inProgress) { + provider.mint(ctx, dso, workspaceFilter); + DOI newDoi = doiService.findDOIByDSpaceObject(ctx, dso); + if (newDoi != null) { + newDoi.setStatus(DOIIdentifierProvider.PENDING); + doiService.update(ctx, newDoi); } } else { - log.debug("DOIConsumer cannot handles items without DOIs, skipping: " - + event.toString()); - return; + log.debug("DOIConsumer cannot handles items without DOIs, skipping: " + event.toString()); } - } else if (ContentServiceFactory.getInstance().getWorkspaceItemService().findByItem(ctx, item) != null - || WorkflowServiceFactory.getInstance().getWorkflowItemService().findByItem(ctx, item) != null) { - if (DSpaceServicesFactory.getInstance().getConfigurationService() - .getBooleanProperty("identifiers.submission.register", false)) { - // We have a DOI for an in-progress submission item -- if the filter says "no", we should delete - // the minted DOI so it doesn't get registered on item install. + } else { + // If in progress, we can also switch PENDING and MINTED status depending on the latest filter + // evaluation + if (inProgress) { try { - provider.checkMintable(ctx, item); - } catch (DOIIdentifierNotApplicableException e) { - log.debug("Consumer deleting DOI for in-progress item: " + doi); - provider.delete(ctx, item, doi); - return; + // Check the filter + provider.checkMintable(ctx, workspaceFilter, dso); + // If we made it here, the existing doi should be back to PENDING + if (DOIIdentifierProvider.MINTED.equals(doi.getStatus())) { + doi.setStatus(DOIIdentifierProvider.PENDING); + } + } catch (IdentifierNotApplicableException e) { + // Set status to MINTED if configured to downgrade existing DOIs + if (configurationService + .getBooleanProperty("identifiers.submission.strip_pending_during_submission", true)) { + doi.setStatus(DOIIdentifierProvider.MINTED); + } } + doiService.update(ctx, doi); } else { - log.debug("Consumer skipping mint for item as it is in-progress and" + - "identifiers.submission.register=false: " + item.getID()); + try { + provider.updateMetadata(ctx, dso, doi.getDoi()); + } catch (IllegalArgumentException ex) { + // should not happen, as we got the DOI from the DOIProvider + log.warn("DOIConsumer caught an IdentifierException.", ex); + } catch (IdentifierException ex) { + log.warn("DOIConsumer cannot update metadata for Item with ID " + + item.getID() + " and DOI " + doi + ".", ex); + } } - } - try { - provider.updateMetadata(ctx, dso, doi); - } catch (IllegalArgumentException ex) { - // should not happen, as we got the DOI from the DOIProvider - log.warn("DOIConsumer caught an IdentifierException.", ex); - } catch (IdentifierException ex) { - log.warn("DOIConsumer cannot update metadata for Item with ID " - + item.getID() + " and DOI " + doi + ".", ex); + ctx.commit(); } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java index e0e0da9440dd..8af7f49e300b 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java @@ -30,6 +30,9 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.logic.Filter; +import org.dspace.content.logic.FilterUtils; +import org.dspace.content.logic.TrueFilter; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -61,7 +64,8 @@ public class DOIOrganiser { protected ItemService itemService; protected DOIService doiService; protected ConfigurationService configurationService; - protected boolean skipFilter; + // This filter will override the default provider filter / behaviour + protected Filter filter; /** * Constructor to be called within the main() method @@ -76,7 +80,7 @@ public DOIOrganiser(Context context, DOIIdentifierProvider provider) { this.itemService = ContentServiceFactory.getInstance().getItemService(); this.doiService = IdentifierServiceFactory.getInstance().getDOIService(); this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - this.skipFilter = false; + this.filter = new TrueFilter(); } /** @@ -121,12 +125,13 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args "Perform online metadata update for all identifiers queued for metadata update."); options.addOption("d", "delete-all", false, "Perform online deletion for all identifiers queued for deletion."); - options.addOption("q", "quiet", false, "Turn the command line output off."); - options.addOption(null, "skip-filter", false, - "Skip the configured item filter when registering or reserving."); + Option filterDoi = Option.builder().optionalArg(true).longOpt("filter").hasArg().argName("filterName") + .desc("Use the specified filter name instead of the provider's filter. Defaults to a special " + + "'always true' filter to force operations").build(); + options.addOption(filterDoi); Option registerDoi = Option.builder() .longOpt("register-doi") @@ -203,10 +208,12 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args } DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); - // Should we skip the filter? - if (line.hasOption("skip-filter")) { - System.out.println("Skipping the item filter"); - organiser.skipFilter = true; + // Do we get a filter? + if (line.hasOption("filter")) { + String filter = line.getOptionValue("filter"); + if (null != filter) { + organiser.filter = FilterUtils.getFilterFromConfiguration(filter); + } } if (line.hasOption('s')) { @@ -394,19 +401,18 @@ public void list(String processName, PrintStream out, PrintStream err, Integer . /** * Register DOI with the provider * @param doiRow - doi to register - * @param skipFilter - whether filters should be skipped before registration + * @param filter - logical item filter to override * @throws SQLException * @throws DOIIdentifierException */ - public void register(DOI doiRow, boolean skipFilter) throws SQLException, DOIIdentifierException { + public void register(DOI doiRow, Filter filter) throws SQLException, DOIIdentifierException { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { throw new IllegalArgumentException("Currenty DSpace supports DOIs for Items only."); } try { - provider.registerOnline(context, dso, - DOI.SCHEME + doiRow.getDoi()); + provider.registerOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), filter); if (!quiet) { System.out.println("This identifier: " @@ -466,29 +472,23 @@ public void register(DOI doiRow, boolean skipFilter) throws SQLException, DOIIde } /** - * Register DOI with the provider, always applying (ie. never skipping) any configured filters + * Register DOI with the provider * @param doiRow - doi to register * @throws SQLException * @throws DOIIdentifierException */ public void register(DOI doiRow) throws SQLException, DOIIdentifierException { - if (this.skipFilter) { - System.out.println("Skipping the filter for " + doiRow.getDoi()); - } - register(doiRow, this.skipFilter); + register(doiRow, this.filter); } /** - * Reserve DOI with the provider, always applying (ie. never skipping) any configured filters + * Reserve DOI with the provider, * @param doiRow - doi to reserve * @throws SQLException * @throws DOIIdentifierException */ public void reserve(DOI doiRow) { - if (this.skipFilter) { - System.out.println("Skipping the filter for " + doiRow.getDoi()); - } - reserve(doiRow, this.skipFilter); + reserve(doiRow, this.filter); } /** @@ -497,14 +497,14 @@ public void reserve(DOI doiRow) { * @throws SQLException * @throws DOIIdentifierException */ - public void reserve(DOI doiRow, boolean skipFilter) { + public void reserve(DOI doiRow, Filter filter) { DSpaceObject dso = doiRow.getDSpaceObject(); if (Constants.ITEM != dso.getType()) { throw new IllegalArgumentException("Currently DSpace supports DOIs for Items only."); } try { - provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), skipFilter); + provider.reserveOnline(context, dso, DOI.SCHEME + doiRow.getDoi(), filter); if (!quiet) { System.out.println("This identifier : " + DOI.SCHEME + doiRow.getDoi() + " is successfully reserved."); @@ -699,7 +699,7 @@ public DOI resolveToDOI(String identifier) //Check if this Item has an Identifier, mint one if it doesn't if (null == doiRow) { - doi = provider.mint(context, dso, this.skipFilter); + doi = provider.mint(context, dso, this.filter); doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); return doiRow; @@ -723,7 +723,7 @@ public DOI resolveToDOI(String identifier) doiRow = doiService.findDOIByDSpaceObject(context, dso); if (null == doiRow) { - doi = provider.mint(context, dso, this.skipFilter); + doi = provider.mint(context, dso, this.filter); doiRow = doiService.findByDoi(context, doi.substring(DOI.SCHEME.length())); } diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java index 64eee1dfcf32..568e4e25a533 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java @@ -9,9 +9,11 @@ import java.sql.SQLException; import java.util.List; +import java.util.Map; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; +import org.dspace.content.logic.Filter; import org.dspace.core.Context; import org.dspace.identifier.Identifier; import org.dspace.identifier.IdentifierException; @@ -103,6 +105,40 @@ void reserve(Context context, DSpaceObject dso, String identifier) */ void register(Context context, DSpaceObject dso) throws AuthorizeException, SQLException, IdentifierException; + /** + * @param context The relevant DSpace Context. + * @param dso DSpace object to be registered + * @param typeFilters If a service supports a given Identifier implementation, apply the associated filter + * @throws AuthorizeException if authorization error + * @throws SQLException if database error + * @throws IdentifierException if identifier error + */ + void register(Context context, DSpaceObject dso, Map, Filter> typeFilters) + throws AuthorizeException, SQLException, IdentifierException; + + /** + * @param context The relevant DSpace Context. + * @param dso DSpace object to be registered + * @param type Type of identifier to register + * @param filter If a service supports a given Identifier implementation, apply this specific filter + * @throws AuthorizeException if authorization error + * @throws SQLException if database error + * @throws IdentifierException if identifier error + */ + void register(Context context, DSpaceObject dso, Class type, Filter filter) + throws AuthorizeException, SQLException, IdentifierException; + + /** + * @param context The relevant DSpace Context. + * @param dso DSpace object to be registered + * @param type Type of identifier to register + * @throws AuthorizeException if authorization error + * @throws SQLException if database error + * @throws IdentifierException if identifier error + */ + void register(Context context, DSpaceObject dso, Class type) + throws AuthorizeException, SQLException, IdentifierException; + /** * Used to Register a specific Identifier (for example a Handle, hdl:1234.5/6). * The provider is responsible for detecting and processing the appropriate diff --git a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java index b9dbbba6473b..c891589cff07 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java @@ -36,6 +36,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.logic.DefaultFilter; import org.dspace.content.logic.LogicalStatement; +import org.dspace.content.logic.TrueFilter; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.ItemService; @@ -128,7 +129,7 @@ public void init() { provider.itemService = itemService; provider.setConfigurationService(config); provider.setDOIConnector(connector); - provider.setFilterService(null); + provider.setFilter(null); } catch (AuthorizeException ex) { log.error("Authorization Error in init", ex); fail("Authorization Error in init: " + ex.getMessage()); @@ -504,7 +505,7 @@ public void testMintDOI() String doi = null; try { // get a DOI (skipping any filters) - doi = provider.mint(context, item, true); + doi = provider.mint(context, item); } catch (IdentifierException e) { e.printStackTrace(System.err); fail("Got an IdentifierException: " + e.getMessage()); @@ -544,23 +545,18 @@ public void testMint_DOI_withNonMatchingFilter() Item item = newItem(); boolean wasFiltered = false; try { - // Temporarily set the provider to have a filter that always returns false for an item - // (therefore, the item should be 'filtered' out and not apply to this minting request) + // Mint this with the filter DefaultFilter doiFilter = new DefaultFilter(); LogicalStatement alwaysFalse = (context, i) -> false; doiFilter.setStatement(alwaysFalse); - provider.setFilterService(doiFilter); // get a DOI with the method that applies filters by default - provider.mint(context, item); + provider.mint(context, item, doiFilter); } catch (DOIIdentifierNotApplicableException e) { // This is what we wanted to see - we can return safely wasFiltered = true; } catch (IdentifierException e) { e.printStackTrace(); fail("Got an IdentifierException: " + e.getMessage()); - } finally { - // Set filter service back to null - provider.setFilterService(null); } // Fail the test if the filter didn't throw a "not applicable" exception assertTrue("DOI minting attempt was not filtered by filter service", wasFiltered); @@ -583,17 +579,14 @@ public void testMint_DOI_withMatchingFilter() DefaultFilter doiFilter = new DefaultFilter(); LogicalStatement alwaysTrue = (context, i) -> true; doiFilter.setStatement(alwaysTrue); - provider.setFilterService(doiFilter); // get a DOI with the method that applies filters by default - doi = provider.mint(context, item); + doi = provider.mint(context, item, doiFilter); } catch (DOIIdentifierNotApplicableException e) { // This is what we wanted to see - we can return safely wasFiltered = true; } catch (IdentifierException e) { e.printStackTrace(); fail("Got an IdentifierException: " + e.getMessage()); - } finally { - provider.setFilterService(null); } // If the attempt was filtered, fail assertFalse("DOI minting attempt was incorrectly filtered by filter service", wasFiltered); @@ -665,7 +658,7 @@ public void testCreate_and_Register_DOI() Item item = newItem(); // Register, skipping the filter - String doi = provider.register(context, item, true); + String doi = provider.register(context, item, new TrueFilter()); // we want the created DOI to be returned in the following format: // doi:10./. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java new file mode 100644 index 000000000000..1321ebe9929a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java @@ -0,0 +1,144 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.MetadataConverter; +import org.dspace.app.rest.model.IdentifierRest; +import org.dspace.app.rest.model.IdentifiersRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.repository.ItemRestRepository; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.logic.TrueFilter; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.IdentifierNotFoundException; +import org.dspace.identifier.service.DOIService; +import org.dspace.identifier.service.IdentifierService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller to register identifiers + */ +@RestController +@RequestMapping("/api/" + ItemRest.CATEGORY + "/" + ItemRest.PLURAL_NAME + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + + "/" + IdentifiersRest.PLURAL_NAME) +public class ItemIdentifierController { + + @Autowired + ConverterService converter; + + @Autowired + ItemService itemService; + + @Autowired + ItemRestRepository itemRestRepository; + + @Autowired + MetadataConverter metadataConverter; + + @Autowired + IdentifierService identifierService; + + @Autowired + DOIService doiService; + + @Autowired + Utils utils; + + public IdentifiersRest get(@PathVariable UUID uuid, HttpServletRequest request, + HttpServletResponse response) + throws SQLException, AuthorizeException { + Context context = ContextUtil.obtainContext(request); + Item item = itemService.find(context, uuid); + if (item == null) { + throw new ResourceNotFoundException("Could not find item with id " + uuid); + } + IdentifiersRest identifiersRest = new IdentifiersRest(); + List identifierRests = new ArrayList<>(); + DOI doi = doiService.findDOIByDSpaceObject(context, item); + String handle = HandleServiceFactory.getInstance().getHandleService().findHandle(context, item); + try { + if (doi != null) { + String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); + IdentifierRest identifierRest = new IdentifierRest(doiUrl, "doi", String.valueOf(doi.getStatus())); + identifierRests.add(identifierRest); + } + if (handle != null) { + identifierRests.add(new IdentifierRest(handle, "handle", null)); + } + } catch (IdentifierException e) { + throw new IllegalStateException("Failed to register identifier: " + e.getMessage()); + } + + return identifiersRest; + } + + /** + * Method to register an identifier for an item. + * + * @return OK + */ + @RequestMapping(method = RequestMethod.POST) + @PreAuthorize("hasPermission(#uuid, 'ITEM', 'ADMIN')") + public ResponseEntity> registerIdentifierForItem(@PathVariable UUID uuid, + HttpServletRequest request, + HttpServletResponse response) + throws SQLException, AuthorizeException { + Context context = ContextUtil.obtainContext(request); + + Item item = itemService.find(context, uuid); + + if (item == null) { + throw new ResourceNotFoundException("Could not find item with id " + uuid); + } + + String identifier = null; + try { + DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); + doiIdentifierProvider.register(context, item, new TrueFilter()); + if (context != null) { + context.commit(); + } + } catch (IdentifierNotFoundException e) { + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } catch (IdentifierException e) { + throw new IllegalStateException("Failed to register identifier: " + identifier); + } + return ControllerUtils.toEmptyResponse(HttpStatus.CREATED); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java new file mode 100644 index 000000000000..dc0e150311e2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -0,0 +1,67 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Implementation of IdentifierRest REST resource, representing some DSpace identifier + * for use with the REST API + * + * @author Kim Shepherd + */ +public class IdentifierRest implements RestModel { + + // Set names used in component wiring + public static final String NAME = "identifier"; + public static final String PLURAL_NAME = "identifiers"; + private String value; + private String identifierType; + private String identifierStatus; + + // Empty constructor + public IdentifierRest() { + } + + public IdentifierRest(String value, String identifierType, String identifierStatus) { + this.value = value; + this.identifierType = identifierType; + this.identifierStatus = identifierStatus; + } + + // Return name for getType() + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getIdentifierType() { + return identifierType; + } + + public void setIdentifierType(String identifierType) { + this.identifierType = identifierType; + } + + public String getIdentifierStatus() { + return identifierStatus; + } + + public void setIdentifierStatus(String identifierStatus) { + this.identifierStatus = identifierStatus; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java new file mode 100644 index 000000000000..bd7ead511f08 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java @@ -0,0 +1,47 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + *

+ * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Implementation of IdentifierRest REST resource, representing a list of all identifiers + * for use with the REST API + * + * @author Kim Shepherd + */ +public class IdentifiersRest implements RestModel { + + // Set names used in component wiring + public static final String NAME = "identifier"; + public static final String PLURAL_NAME = "identifiers"; + private List identifiers; + + // Empty constructor + public IdentifiersRest() { + identifiers = new ArrayList<>(); + } + + // Return name for getType() + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + public List getIdentifiers() { + return identifiers; + } + + public void setIdentifiers(List identifiers) { + this.identifiers = identifiers; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java index 63004b68d226..dcb68793a71e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -25,6 +25,10 @@ name = ItemRest.BUNDLES, method = "getBundles" ), + //@LinkRest( + // name = ItemRest.IDENTIFIERS, + // method = "getIdentifiers" + //), @LinkRest( name = ItemRest.MAPPED_COLLECTIONS, method = "getMappedCollections" @@ -57,6 +61,7 @@ public class ItemRest extends DSpaceObjectRest { public static final String ACCESS_STATUS = "accessStatus"; public static final String BUNDLES = "bundles"; + public static final String IDENTIFIERS = "identifiers"; public static final String MAPPED_COLLECTIONS = "mappedCollections"; public static final String OWNING_COLLECTION = "owningCollection"; public static final String RELATIONSHIPS = "relationships"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java index c487d3769ebc..7e9097037789 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java @@ -20,6 +20,10 @@ public class DataIdentifiers implements SectionData { String doi; List otherIdentifiers; + public DataIdentifiers() { + + } + public String getHandle() { return handle; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 16ce8629d17d..42e4425e2c4e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -45,6 +45,10 @@ import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; +import org.dspace.identifier.service.IdentifierService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -92,6 +96,9 @@ public class ItemRestRepository extends DSpaceObjectRestRepository stringList) throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java index 2bdfbbde80ef..173ac4c500fe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.submit.step; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -26,6 +27,7 @@ import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.Handle; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.factory.IdentifierServiceFactory; @@ -90,20 +92,31 @@ private DataIdentifiers getIdentifierData(InProgressSubmission obj) { IdentifierServiceFactory.getInstance().getIdentifierService(); // Attempt to look up handle and DOI identifiers for this item String handle = identifierService.lookup(context, obj.getItem(), Handle.class); - String doi = identifierService.lookup(context, obj.getItem(), DOI.class); + String simpleDoi = identifierService.lookup(context, obj.getItem(), DOI.class); + DOI doi = null; + String doiString = null; + try { + doi = IdentifierServiceFactory.getInstance().getDOIService().findDOIByDSpaceObject(context, obj.getItem()); + if (doi != null && !DOIIdentifierProvider.MINTED.equals(doi.getStatus()) + && !DOIIdentifierProvider.MINTED.equals(doi.getStatus())) { + doiString = doi.getDoi(); + } + } catch (SQLException e) { + log.error(e.getMessage()); + } // Look up all identifiers and if they're not the DOI or handle, add them to the 'other' list List otherIdentifiers = new ArrayList<>(); for (String identifier : identifierService.lookup(context, obj.getItem())) { - if (!StringUtils.equals(doi, identifier) && !StringUtils.equals(handle, identifier)) { + if (!StringUtils.equals(simpleDoi, identifier) && !StringUtils.equals(handle, identifier)) { otherIdentifiers.add(identifier); } } // If we got a DOI, format it to its external form - if (StringUtils.isNotEmpty(doi)) { + if (StringUtils.isNotEmpty(doiString)) { try { - doi = IdentifierServiceFactory.getInstance().getDOIService().DOIToExternalForm(doi); + doiString = IdentifierServiceFactory.getInstance().getDOIService().DOIToExternalForm(doiString); } catch (IdentifierException e) { log.error("Error formatting DOI: " + doi); } @@ -114,7 +127,7 @@ private DataIdentifiers getIdentifierData(InProgressSubmission obj) { } // Populate bean with data and return - result.setDoi(doi); + result.setDoi(doiString); result.setHandle(handle); result.setOtherIdentifiers(otherIdentifiers); diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 3ad984ec9757..2aa1646bfa54 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -240,7 +240,7 @@ - + diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg index 3e0992e090ee..e0e23a5a2c67 100644 --- a/dspace/config/modules/identifiers.cfg +++ b/dspace/config/modules/identifiers.cfg @@ -5,7 +5,35 @@ # as the Show Identifiers step which can "pre-mint" DOIs and Handles # #----------------------------------------------------------------------# -# Should identifiers be registered in the Show Identifiers step and minted / updated / deleted as appropriate -# in the DOI Consumer? Setting this property to 'true' is recommended +# Should DOIs be minted for (future) registration at workspace item creation? +# A DOI created at this stage will be in a 'PENDING' status while in workspace and workflow. +# At the time of item install, the DOI filter (if any) will be applied and if the item matches the filter, the DOI +# status will be updated to TO_BE_REGISTERED. An administrator can also manually progress the DOI status, overriding +# any filters, in the item status page. +# This option doesn't require the Show Identifiers submission step to be visible. # Default: false -#identifiers.submission.register = false \ No newline at end of file +#identifiers.submission.register = true + +# This configuration property can be set to a filter name to determine if a PENDING DOI for an item +# should be queued for registration. If the filter doesn't match, the DOI will stay in PENDING or MINTED status +# so that the identifier itself persists in case it is considered for registration in the future. +# See doi-filter and other example filters in item-filters.xml. +# Default (always_true_filter) +#identifiers.submission.filter.install = doi-filter + +# This optional configuration property can be set to a filter name, in case there are some initial rules to apply +# when first deciding whether a DOI should be be created for a new workspace item with a PENDING status. +# This filter is only applied if identifiers.submission.register is true. +# This filter is updated as submission data is saved. +# Default: (always_true_filter) +#identifiers.submission.filter.workspace = always_true_filter + +# If true, the workspace filter will be applied as submission data is saved. If the filter no longer +# matches the item, the DOI will be shifted into a MINTED status and not displayed in the submission section. +# If false, then once a DOI has been created with PENDING status it will remain that way until final item install +# Default: true +#identifiers.submission.strip_pending_during_submission = true + +# This configuration property can be set to a filter name to determine if an item processed by RegisterDOI curation +# task should be eligible for a DOI +#identifiers.submission.filter.curation = always_true_filter \ No newline at end of file diff --git a/dspace/config/spring/api/identifier-service.xml b/dspace/config/spring/api/identifier-service.xml index e9f08003bd63..dbcd49df0e92 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -13,7 +13,7 @@ scope="singleton"/> @@ -42,7 +42,7 @@ a DOIConnector that handles all API calls to your DOI registration agency. Please configure a DOIConnector as well! --> - - + - - + + + + + + - - - + @@ -226,16 +241,12 @@ - - - + - - - + + + + + + + + + + + + + + + + From dcdbad7d02938f6a4ec5cc3cca7c78725eb5aec2 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 25 Aug 2022 13:36:16 +1200 Subject: [PATCH 264/394] [TLC-249] Add and expose config for enabling register DOI --- dspace/config/modules/identifiers.cfg | 6 +++++- dspace/config/modules/rest.cfg | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg index e0e23a5a2c67..8280a7e06021 100644 --- a/dspace/config/modules/identifiers.cfg +++ b/dspace/config/modules/identifiers.cfg @@ -36,4 +36,8 @@ # This configuration property can be set to a filter name to determine if an item processed by RegisterDOI curation # task should be eligible for a DOI -#identifiers.submission.filter.curation = always_true_filter \ No newline at end of file +#identifiers.submission.filter.curation = always_true_filter + +# Show Register DOI button in item status page? +# Default: false +#identifiers.item-status.register = true \ No newline at end of file diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 5bb58b0eee30..754e7b1e262f 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -46,6 +46,7 @@ rest.properties.exposed = google.recaptcha.key.site rest.properties.exposed = google.recaptcha.version rest.properties.exposed = google.recaptcha.mode rest.properties.exposed = cc.license.jurisdiction +rest.properties.exposed = identifiers.item-status.register rest.properties.exposed = authentication-password.domain.valid #---------------------------------------------------------------# From 73406b7ff03bdcf9fa4f849ab539b5daf7122850 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 26 Aug 2022 09:37:21 +1200 Subject: [PATCH 265/394] [TLC-249] REST backend for identifier config --- .../app/rest/ItemIdentifierController.java | 30 +++++++---- .../impl/CanRegisterDOIFeature.java | 53 +++++++++++++++++++ .../org/dspace/app/rest/model/ItemRest.java | 8 +-- .../rest/repository/ItemRestRepository.java | 38 ------------- 4 files changed, 76 insertions(+), 53 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java index 1321ebe9929a..48d1a613bea5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java @@ -21,6 +21,7 @@ import org.dspace.app.rest.model.IdentifierRest; import org.dspace.app.rest.model.IdentifiersRest; import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.hateoas.ItemResource; import org.dspace.app.rest.repository.ItemRestRepository; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; @@ -41,6 +42,7 @@ import org.springframework.data.rest.webmvc.ControllerUtils; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.hateoas.RepresentationModel; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -78,6 +80,7 @@ public class ItemIdentifierController { @Autowired Utils utils; + @RequestMapping(method = RequestMethod.GET) public IdentifiersRest get(@PathVariable UUID uuid, HttpServletRequest request, HttpServletResponse response) throws SQLException, AuthorizeException { @@ -87,22 +90,22 @@ public IdentifiersRest get(@PathVariable UUID uuid, HttpServletRequest request, throw new ResourceNotFoundException("Could not find item with id " + uuid); } IdentifiersRest identifiersRest = new IdentifiersRest(); - List identifierRests = new ArrayList<>(); + List identifierRestList = new ArrayList<>(); DOI doi = doiService.findDOIByDSpaceObject(context, item); String handle = HandleServiceFactory.getInstance().getHandleService().findHandle(context, item); try { if (doi != null) { String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); IdentifierRest identifierRest = new IdentifierRest(doiUrl, "doi", String.valueOf(doi.getStatus())); - identifierRests.add(identifierRest); + identifierRestList.add(identifierRest); } if (handle != null) { - identifierRests.add(new IdentifierRest(handle, "handle", null)); + identifierRestList.add(new IdentifierRest(handle, "handle", null)); } } catch (IdentifierException e) { throw new IllegalStateException("Failed to register identifier: " + e.getMessage()); } - + identifiersRest.setIdentifiers(identifierRestList); return identifiersRest; } @@ -114,12 +117,13 @@ public IdentifiersRest get(@PathVariable UUID uuid, HttpServletRequest request, @RequestMapping(method = RequestMethod.POST) @PreAuthorize("hasPermission(#uuid, 'ITEM', 'ADMIN')") public ResponseEntity> registerIdentifierForItem(@PathVariable UUID uuid, - HttpServletRequest request, - HttpServletResponse response) + HttpServletRequest request, + HttpServletResponse response) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainContext(request); Item item = itemService.find(context, uuid); + ItemRest itemRest; if (item == null) { throw new ResourceNotFoundException("Could not find item with id " + uuid); @@ -129,16 +133,20 @@ public ResponseEntity> registerIdentifierForItem(@PathVar try { DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); - doiIdentifierProvider.register(context, item, new TrueFilter()); - if (context != null) { - context.commit(); + if (doiIdentifierProvider != null) { + doiIdentifierProvider.register(context, item, new TrueFilter()); } } catch (IdentifierNotFoundException e) { - return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + // Log? + return converter.toRest(item, utils.obtainProjection()); } catch (IdentifierException e) { throw new IllegalStateException("Failed to register identifier: " + identifier); } - return ControllerUtils.toEmptyResponse(HttpStatus.CREATED); + itemService.updateLastModified(context, item); + itemRest = converter.toRest(item, utils.obtainProjection()); + context.complete(); + ItemResource itemResource = converter.toResource(itemRest); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), itemResource); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java new file mode 100644 index 000000000000..042e8c4aaf94 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java @@ -0,0 +1,53 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.authorization.impl; +import java.sql.SQLException; + +import org.dspace.app.rest.authorization.AuthorizationFeature; +import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation; +import org.dspace.app.rest.authorization.AuthorizeServiceRestUtil; +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.security.DSpaceRestPermission; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Can the current user register a DOI for this item? + * + * @author Kim Shepherd + */ +@Component +@AuthorizationFeatureDocumentation(name = CanRegisterDOIFeature.NAME, + description = "It can be used to verify if the user can register a DOI for this item") +public class CanRegisterDOIFeature implements AuthorizationFeature { + + @Autowired + private AuthorizeServiceRestUtil authorizeServiceRestUtil; + + public static final String NAME = "canRegisterDOI"; + + @Override + @SuppressWarnings("rawtypes") + public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + if (object instanceof ItemRest) { + return authorizeServiceRestUtil.authorizeActionBoolean(context, object, DSpaceRestPermission.ADMIN); + } + return false; + } + + @Override + public String[] getSupportedTypes() { + return new String[]{ + ItemRest.CATEGORY + "." + ItemRest.NAME + }; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java index dcb68793a71e..24f927cda997 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -25,10 +25,10 @@ name = ItemRest.BUNDLES, method = "getBundles" ), - //@LinkRest( - // name = ItemRest.IDENTIFIERS, - // method = "getIdentifiers" - //), + @LinkRest( + name = ItemRest.IDENTIFIERS, + method = "getIdentifiers" + ), @LinkRest( name = ItemRest.MAPPED_COLLECTIONS, method = "getMappedCollections" diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 42e4425e2c4e..f2adba61a8f9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -364,44 +364,6 @@ public Bundle addBundleToItem(Context context, Item item, BundleRest bundleRest) return bundle; } - /** - * Register an identifier for an item - * @param context - * @param item - * @param identifier - * @return - * @throws SQLException - * @throws AuthorizeException - * @throws IdentifierException - */ - @PreAuthorize("hasPermission(#uuid, 'ITEM', 'ADMIN')") - public boolean registerIdentifier(Context context, Item item, String identifier) - throws SQLException, AuthorizeException, IdentifierException { - if (item != null) { - if (identifier != null) { - // Try to register the supplied identifier - identifierService.register(context, item, identifier); - return item.equals(identifierService.resolve(context, identifier)); - } else { - DSpaceServicesFactory.getInstance().getServiceManager() - .getServicesByType(VersionedHandleIdentifierProviderWithCanonicalHandles.class); - // Call plain 'register' which will mint a new DOI, handle, etc if needed - // this NOT compatible with versioned handle provider with canonical handles - boolean compatible = DSpaceServicesFactory.getInstance().getServiceManager() - .getServicesByType(VersionedHandleIdentifierProviderWithCanonicalHandles.class).isEmpty(); - if (compatible) { - // Register without a supplied identifier - identifierService.register(context, item); - return true; - } else { - log.error("This register method is NOT compatible with" + - "VersionedHandleIdentifierProviderWithCanonicalHandles"); - } - } - } - return false; - } - @Override protected ItemRest createAndReturn(Context context, List stringList) throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { From 3373cf333000e07b38866a1f9651ab610c5e8496 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 31 Aug 2022 16:23:13 +1200 Subject: [PATCH 266/394] [TLC-249] Better status return for new DOIs, write new integration tests --- .../app/rest/ItemIdentifierController.java | 38 +++- .../app/rest/ItemIdentifierControllerIT.java | 169 ++++++++++++++++++ 2 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java index 48d1a613bea5..f02f2278665c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java @@ -80,6 +80,17 @@ public class ItemIdentifierController { @Autowired Utils utils; + /** + * Get list of identifiers associated with this item, along with type (eg doi, handle) + * and status (eg PENDING, REGISTERED) + * + * @param uuid + * @param request + * @param response + * @return + * @throws SQLException + * @throws AuthorizeException + */ @RequestMapping(method = RequestMethod.GET) public IdentifiersRest get(@PathVariable UUID uuid, HttpServletRequest request, HttpServletResponse response) @@ -110,9 +121,10 @@ public IdentifiersRest get(@PathVariable UUID uuid, HttpServletRequest request, } /** - * Method to register an identifier for an item. + * Queue a new, pending or minted DOI for registration for a given item. Requires administrative privilege. + * This request is sent from the Register DOI button (configurable) on the item status page. * - * @return OK + * @return 302 FOUND if the DOI is already registered or reserved, 201 CREATED if queued for registration */ @RequestMapping(method = RequestMethod.POST) @PreAuthorize("hasPermission(#uuid, 'ITEM', 'ADMIN')") @@ -130,23 +142,37 @@ public ResponseEntity> registerIdentifierForItem(@PathVar } String identifier = null; + HttpStatus httpStatus = HttpStatus.BAD_REQUEST; try { DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); if (doiIdentifierProvider != null) { - doiIdentifierProvider.register(context, item, new TrueFilter()); + Integer doiStatus = doiService.findDOIByDSpaceObject(context, item).getStatus(); + // Check if this DOI has a status which makes it eligible for registration + if (null == doiStatus || DOIIdentifierProvider.MINTED.equals(doiStatus) + || DOIIdentifierProvider.PENDING.equals(doiStatus)) { + // Mint identifier and return 201 CREATED + doiIdentifierProvider.register(context, item, new TrueFilter()); + httpStatus = HttpStatus.CREATED; + } else { + // This DOI exists and isn't in a state where it can be queued for registration + // We'll return 302 FOUND to indicate it's here and not an error, but no creation was performed + httpStatus = HttpStatus.FOUND; + } } } catch (IdentifierNotFoundException e) { - // Log? - return converter.toRest(item, utils.obtainProjection()); + httpStatus = HttpStatus.NOT_FOUND; } catch (IdentifierException e) { throw new IllegalStateException("Failed to register identifier: " + identifier); } + // We didn't exactly change the item, but we did queue an identifier which is closely associated with it + // so we should update the last modified date here itemService.updateLastModified(context, item); itemRest = converter.toRest(item, utils.obtainProjection()); context.complete(); ItemResource itemResource = converter.toResource(itemRest); - return ControllerUtils.toResponseEntity(HttpStatus.CREATED, new HttpHeaders(), itemResource); + // Return the status and item resource + return ControllerUtils.toResponseEntity(httpStatus, new HttpHeaders(), itemResource); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java new file mode 100644 index 000000000000..29a322c2fde3 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java @@ -0,0 +1,169 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.DOIService; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * Test getting and registering item identifiers + * + * @author Kim Shepherd + */ +public class ItemIdentifierControllerIT extends AbstractControllerIntegrationTest { + @Before + public void setup() throws Exception { + super.setUp(); + } + + @Test + public void testRegisterDoiForItem() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + // A non-admin should get an unauthorised error from REST method preauth + // Expect first forbidden + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isForbidden()); + // Now, unauthorised + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isUnauthorized()); + + // Set token to admin credentials + token = getAuthToken(admin.getEmail(), password); + + // Expect a successful 201 CREATED for this item with no DOI + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isCreated()); + + // Expected 302 FOUND status code for a DOI already in REGISTERED / TO_BE_REGISTERED state + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isFound()); + + // Get the doi we minted and queued for registration + DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); + // The DOI should not be null + Assert.assertNotNull(doi); + // The DOI status should be TO_BE_REGISTERED + Assert.assertEquals(DOIIdentifierProvider.TO_BE_REGISTERED, doi.getStatus()); + + // Now, set the DOI status back to pending and update + doi.setStatus(DOIIdentifierProvider.PENDING); + doiService.update(context, doi); + + // Do another POST, again this should return 201 CREATED as we shift the DOI from PENDING to TO_BE_REGISTERED + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isCreated()); + + context.restoreAuthSystemState(); + } + + @Test + public void testGetIdentifiersForItem() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + // Use the DOI service to directly create and manipulate a DOI on this object so that this IT is fully + // separate from the other integrated behaviour tested in the previous test + DOI doi = doiService.create(context); + doi.setDSpaceObject(publicItem1); + String doiString = "dspace/" + publicItem1.getHandle(); + doi.setDoi(doiString); + doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); + doiService.update(context, doi); + + // Expect first forbidden + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isForbidden()); + + // Get identifiers for this item - we expect a 200 OK response and the type of identifier + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()).andExpect(jsonPath("$.type").value("identifier")); + + // Expect an array of identifiers + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.identifiers").isArray()); + + // Expect a valid DOI with the value, type and status we expect + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.identifiers[0].type").value("identifier")) + .andExpect(jsonPath("$.identifiers[0].value").value(doiString)) + .andExpect(jsonPath("$.identifiers[0].identifierType").value("doi")) + .andExpect(jsonPath("$.identifiers[0].identifierStatus") + .value(DOIIdentifierProvider.IS_REGISTERED.toString())); + + // Expect a valid Handle with the value, type we expect + getClient(token).perform(post("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.identifiers[0].type").value("identifier")) + .andExpect(jsonPath("$.identifiers[0].value").value(publicItem1.getHandle())) + .andExpect(jsonPath("$.identifiers[0].identifierType").value("handle")); + + } +} From 4140470f9de2ac58f9da46f30dd6ca2f2f267dae Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 1 Sep 2022 09:04:36 +1200 Subject: [PATCH 267/394] [TLC-249] Integration tests, keep GET handling in controller --- .../content/InstallItemServiceImpl.java | 2 - .../identifier/IdentifierServiceImpl.java | 2 +- .../app/rest/ItemIdentifierController.java | 22 +++-- .../org/dspace/app/rest/model/ItemRest.java | 2 +- .../repository/ItemIdentifiersRepository.java | 80 +++++++++++++++++++ .../app/rest/ItemIdentifierControllerIT.java | 42 +++++----- 6 files changed, 120 insertions(+), 30 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifiersRepository.java diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index a4913d136032..914355bcb252 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -81,8 +81,6 @@ public Item installItem(Context c, InProgressSubmission is, } else { // This will register the handle but a pending DOI won't be compatible and so won't be registered identifierService.register(c, item, suppliedHandle); - // Continue to register just a DOI - identifierService.register(c, item, DOI.class, filters.get(DOI.class)); } } catch (IdentifierException e) { throw new RuntimeException("Can't create an Identifier!", e); diff --git a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java index 2d41be0daa6a..6a5fea53c4fe 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -182,7 +182,7 @@ public void register(Context context, DSpaceObject dso, Map type : typeFilters.keySet()) { - if (service.supports(type)) { + if (filteredService.supports(type)) { filter = typeFilters.get(type); break; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java index f02f2278665c..23af654d2809 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java @@ -52,7 +52,10 @@ import org.springframework.web.bind.annotation.RestController; /** - * Controller to register identifiers + * Controller to register a DOI for an item, if it has no DOI already, or a DOI in a state where it can be + * advanced to queue for reservation or registration. + * + * @author Kim Shepherd */ @RestController @RequestMapping("/api/" + ItemRest.CATEGORY + "/" + ItemRest.PLURAL_NAME + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID @@ -147,10 +150,19 @@ public ResponseEntity> registerIdentifierForItem(@PathVar DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); if (doiIdentifierProvider != null) { - Integer doiStatus = doiService.findDOIByDSpaceObject(context, item).getStatus(); - // Check if this DOI has a status which makes it eligible for registration - if (null == doiStatus || DOIIdentifierProvider.MINTED.equals(doiStatus) - || DOIIdentifierProvider.PENDING.equals(doiStatus)) { + DOI doi = doiService.findDOIByDSpaceObject(context, item); + boolean exists = false; + boolean pending = false; + if (null != doi) { + exists = true; + Integer doiStatus = doiService.findDOIByDSpaceObject(context, item).getStatus(); + // Check if this DOI has a status which makes it eligible for registration + if (null == doiStatus || DOIIdentifierProvider.MINTED.equals(doiStatus) + || DOIIdentifierProvider.PENDING.equals(doiStatus)) { + pending = true; + } + } + if (!exists || pending) { // Mint identifier and return 201 CREATED doiIdentifierProvider.register(context, item, new TrueFilter()); httpStatus = HttpStatus.CREATED; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java index 24f927cda997..1254ef8f9372 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -27,7 +27,7 @@ ), @LinkRest( name = ItemRest.IDENTIFIERS, - method = "getIdentifiers" + method = "getIdentifiers" ), @LinkRest( name = ItemRest.MAPPED_COLLECTIONS, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifiersRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifiersRepository.java new file mode 100644 index 000000000000..eb3bb92dd100 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifiersRepository.java @@ -0,0 +1,80 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.IdentifierRest; +import org.dspace.app.rest.model.IdentifiersRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.identifier.DOI; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.service.DOIService; +import org.dspace.identifier.service.IdentifierService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for the identifier of an Item + */ +@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.IDENTIFIERS) +public class ItemIdentifiersRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + @Autowired + ItemService itemService; + + @Autowired + IdentifierService identifierService; + + @Autowired + DOIService doiService; + + @PreAuthorize("hasPermission(#itemId, 'ITEM', 'READ')") + public IdentifiersRest getIdentifiers(@Nullable HttpServletRequest request, + UUID itemId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException { + Context context = ContextUtil.obtainContext(request); + Item item = itemService.find(context, itemId); + if (item == null) { + throw new ResourceNotFoundException("Could not find item with id " + itemId); + } + IdentifiersRest identifiersRest = new IdentifiersRest(); + List identifierRestList = new ArrayList<>(); + DOI doi = doiService.findDOIByDSpaceObject(context, item); + String handle = HandleServiceFactory.getInstance().getHandleService().findHandle(context, item); + try { + if (doi != null) { + String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); + IdentifierRest identifierRest = new IdentifierRest(doiUrl, "doi", String.valueOf(doi.getStatus())); + identifierRestList.add(identifierRest); + } + if (handle != null) { + identifierRestList.add(new IdentifierRest(handle, "handle", null)); + } + } catch (IdentifierException e) { + throw new IllegalStateException("Failed to register identifier: " + e.getMessage()); + } + identifiersRest.setIdentifiers(identifierRestList); + return identifiersRest; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java index 29a322c2fde3..043d7ecf059e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -21,6 +22,7 @@ import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.factory.IdentifierServiceFactory; import org.dspace.identifier.service.DOIService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -57,16 +59,19 @@ public void testRegisterDoiForItem() throws Exception { .withAuthor("Smith, Donald") .build(); + // This item should not have a DOI + DOIIdentifierProvider doiIdentifierProvider = + DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", + org.dspace.identifier.DOIIdentifierProvider.class); + doiIdentifierProvider.delete(context, publicItem1); + // A non-admin should get an unauthorised error from REST method preauth // Expect first forbidden String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(post("/api/core/items/" + publicItem1.getID().toString() + "/identifiers")) .andExpect(status().isForbidden()); - // Now, unauthorised - getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) - .andExpect(status().isUnauthorized()); // Set token to admin credentials token = getAuthToken(admin.getEmail(), password); @@ -121,49 +126,44 @@ public void testGetIdentifiersForItem() throws Exception { .withAuthor("Smith, Donald") .build(); - // Use the DOI service to directly create and manipulate a DOI on this object so that this IT is fully - // separate from the other integrated behaviour tested in the previous test - DOI doi = doiService.create(context); - doi.setDSpaceObject(publicItem1); - String doiString = "dspace/" + publicItem1.getHandle(); + // Use the DOI service to directly manipulate the DOI on this object so that we can predict and + // test values via the REST request + DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); + String doiString = "10.5072/dspace-identifier-test"; doi.setDoi(doiString); doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); doiService.update(context, doi); - // Expect first forbidden String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) - .andExpect(status().isForbidden()); // Get identifiers for this item - we expect a 200 OK response and the type of identifier - getClient(token).perform(post("/api/core/items/" + + getClient(token).perform(get("/api/core/items/" + publicItem1.getID().toString() + "/identifiers")) .andExpect(status().isOk()).andExpect(jsonPath("$.type").value("identifier")); // Expect an array of identifiers - getClient(token).perform(post("/api/core/items/" + + getClient(token).perform(get("/api/core/items/" + publicItem1.getID().toString() + "/identifiers")) .andExpect(status().isOk()) .andExpect(jsonPath("$.identifiers").isArray()); // Expect a valid DOI with the value, type and status we expect - getClient(token).perform(post("/api/core/items/" + + getClient(token).perform(get("/api/core/items/" + publicItem1.getID().toString() + "/identifiers")) .andExpect(status().isOk()) .andExpect(jsonPath("$.identifiers[0].type").value("identifier")) - .andExpect(jsonPath("$.identifiers[0].value").value(doiString)) + .andExpect(jsonPath("$.identifiers[0].value").value(doiService.DOIToExternalForm(doiString))) .andExpect(jsonPath("$.identifiers[0].identifierType").value("doi")) .andExpect(jsonPath("$.identifiers[0].identifierStatus") .value(DOIIdentifierProvider.IS_REGISTERED.toString())); // Expect a valid Handle with the value, type we expect - getClient(token).perform(post("/api/core/items/" + + getClient(token).perform(get("/api/core/items/" + publicItem1.getID().toString() + "/identifiers")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.identifiers[0].type").value("identifier")) - .andExpect(jsonPath("$.identifiers[0].value").value(publicItem1.getHandle())) - .andExpect(jsonPath("$.identifiers[0].identifierType").value("handle")); + .andExpect(jsonPath("$.identifiers[1].type").value("identifier")) + .andExpect(jsonPath("$.identifiers[1].value").value(publicItem1.getHandle())) + .andExpect(jsonPath("$.identifiers[1].identifierType").value("handle")); } } From 12dba51b723badbe11c0b89def28b4722d1fe4a4 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 7 Sep 2022 14:57:48 +1200 Subject: [PATCH 268/394] [TLC-249] Move GET handling back to ItemIdentifierController --- .../app/rest/ItemIdentifierController.java | 2 + .../org/dspace/app/rest/model/ItemRest.java | 3 +- .../repository/ItemIdentifiersRepository.java | 80 ------------------- 3 files changed, 3 insertions(+), 82 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifiersRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java index 23af654d2809..d905579fad62 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java @@ -171,6 +171,8 @@ public ResponseEntity> registerIdentifierForItem(@PathVar // We'll return 302 FOUND to indicate it's here and not an error, but no creation was performed httpStatus = HttpStatus.FOUND; } + } else { + throw new IllegalStateException("No DOI provider is configured"); } } catch (IdentifierNotFoundException e) { httpStatus = HttpStatus.NOT_FOUND; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java index 1254ef8f9372..4aa73354fd25 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -26,8 +26,7 @@ method = "getBundles" ), @LinkRest( - name = ItemRest.IDENTIFIERS, - method = "getIdentifiers" + name = ItemRest.IDENTIFIERS ), @LinkRest( name = ItemRest.MAPPED_COLLECTIONS, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifiersRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifiersRepository.java deleted file mode 100644 index eb3bb92dd100..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifiersRepository.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ - -package org.dspace.app.rest.repository; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; - -import org.dspace.app.rest.model.IdentifierRest; -import org.dspace.app.rest.model.IdentifiersRest; -import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.projection.Projection; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.content.Item; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.handle.factory.HandleServiceFactory; -import org.dspace.identifier.DOI; -import org.dspace.identifier.IdentifierException; -import org.dspace.identifier.service.DOIService; -import org.dspace.identifier.service.IdentifierService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.data.rest.webmvc.ResourceNotFoundException; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.stereotype.Component; - -/** - * Link repository for the identifier of an Item - */ -@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.IDENTIFIERS) -public class ItemIdentifiersRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { - @Autowired - ItemService itemService; - - @Autowired - IdentifierService identifierService; - - @Autowired - DOIService doiService; - - @PreAuthorize("hasPermission(#itemId, 'ITEM', 'READ')") - public IdentifiersRest getIdentifiers(@Nullable HttpServletRequest request, - UUID itemId, - @Nullable Pageable optionalPageable, - Projection projection) throws SQLException { - Context context = ContextUtil.obtainContext(request); - Item item = itemService.find(context, itemId); - if (item == null) { - throw new ResourceNotFoundException("Could not find item with id " + itemId); - } - IdentifiersRest identifiersRest = new IdentifiersRest(); - List identifierRestList = new ArrayList<>(); - DOI doi = doiService.findDOIByDSpaceObject(context, item); - String handle = HandleServiceFactory.getInstance().getHandleService().findHandle(context, item); - try { - if (doi != null) { - String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); - IdentifierRest identifierRest = new IdentifierRest(doiUrl, "doi", String.valueOf(doi.getStatus())); - identifierRestList.add(identifierRest); - } - if (handle != null) { - identifierRestList.add(new IdentifierRest(handle, "handle", null)); - } - } catch (IdentifierException e) { - throw new IllegalStateException("Failed to register identifier: " + e.getMessage()); - } - identifiersRest.setIdentifiers(identifierRestList); - return identifiersRest; - } -} From 7926021887ebca2b955d3c5be8b9e720ef85b720 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 16 Sep 2022 09:55:01 +1200 Subject: [PATCH 269/394] [TLC-249] Checkstyle fixes --- .../main/java/org/dspace/content/InstallItemServiceImpl.java | 5 ----- .../java/org/dspace/identifier/IdentifierServiceImpl.java | 1 - 2 files changed, 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index 914355bcb252..688d924f59eb 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -9,7 +9,6 @@ import java.io.IOException; import java.sql.SQLException; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,7 +18,6 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.logic.Filter; import org.dspace.content.logic.FilterUtils; -import org.dspace.content.logic.TrueFilter; import org.dspace.content.service.CollectionService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; @@ -27,12 +25,9 @@ import org.dspace.core.Context; import org.dspace.embargo.service.EmbargoService; import org.dspace.event.Event; -import org.dspace.identifier.DOI; -import org.dspace.identifier.Handle; import org.dspace.identifier.Identifier; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.IdentifierService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; /** diff --git a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java index 6a5fea53c4fe..bf91bded1ab9 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -19,7 +19,6 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.logic.Filter; -import org.dspace.content.logic.TrueFilter; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; import org.dspace.identifier.service.IdentifierService; From abf65cd48c09bef520828fb54c04ee4c2bf48d65 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 16 Sep 2022 10:17:54 +1200 Subject: [PATCH 270/394] [TLC-249] Licence header fixes --- .../src/main/java/org/dspace/app/rest/model/IdentifierRest.java | 2 +- .../main/java/org/dspace/app/rest/model/IdentifiersRest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java index dc0e150311e2..e6de995eb338 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -2,7 +2,7 @@ * 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; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java index bd7ead511f08..7f73a6eeee31 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java @@ -2,7 +2,7 @@ * 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; From 43c2b20367ee224d1014c2e5a6e71a7d404819fa Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Fri, 16 Sep 2022 10:53:42 +1200 Subject: [PATCH 271/394] [TLC-249] Further checkstyle fixes --- .../app/rest/authorization/impl/CanRegisterDOIFeature.java | 1 - .../org/dspace/app/rest/repository/ItemRestRepository.java | 3 --- 2 files changed, 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java index 042e8c4aaf94..a563c8ec8f24 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java @@ -13,7 +13,6 @@ import org.dspace.app.rest.authorization.AuthorizeServiceRestUtil; 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.security.DSpaceRestPermission; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index f2adba61a8f9..ce9679b8cf18 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -45,10 +45,7 @@ import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; -import org.dspace.identifier.IdentifierException; -import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; import org.dspace.identifier.service.IdentifierService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; From 03fb419e38c084bd171fea7cda9ced6af0c292ae Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 20 Oct 2022 11:01:38 +1300 Subject: [PATCH 272/394] [TLC-249] Fix tests, Refactor things to use a LinkRestRepository for GET, Controller for POST --- .../app/rest/ItemIdentifierController.java | 42 +--------- .../app/rest/model/IdentifiersRest.java | 3 +- .../org/dspace/app/rest/model/ItemRest.java | 3 +- .../model/hateoas/IdentifiersResource.java | 12 +++ .../ItemIdentifierLinkRepository.java | 80 +++++++++++++++++++ .../app/rest/ItemIdentifierControllerIT.java | 14 ++-- .../dspace/app/rest/matcher/ItemMatcher.java | 2 + 7 files changed, 107 insertions(+), 49 deletions(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java index d905579fad62..97e62dcde095 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java @@ -59,7 +59,7 @@ */ @RestController @RequestMapping("/api/" + ItemRest.CATEGORY + "/" + ItemRest.PLURAL_NAME + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID - + "/" + IdentifiersRest.PLURAL_NAME) + + "/" + IdentifiersRest.NAME) public class ItemIdentifierController { @Autowired @@ -83,46 +83,6 @@ public class ItemIdentifierController { @Autowired Utils utils; - /** - * Get list of identifiers associated with this item, along with type (eg doi, handle) - * and status (eg PENDING, REGISTERED) - * - * @param uuid - * @param request - * @param response - * @return - * @throws SQLException - * @throws AuthorizeException - */ - @RequestMapping(method = RequestMethod.GET) - public IdentifiersRest get(@PathVariable UUID uuid, HttpServletRequest request, - HttpServletResponse response) - throws SQLException, AuthorizeException { - Context context = ContextUtil.obtainContext(request); - Item item = itemService.find(context, uuid); - if (item == null) { - throw new ResourceNotFoundException("Could not find item with id " + uuid); - } - IdentifiersRest identifiersRest = new IdentifiersRest(); - List identifierRestList = new ArrayList<>(); - DOI doi = doiService.findDOIByDSpaceObject(context, item); - String handle = HandleServiceFactory.getInstance().getHandleService().findHandle(context, item); - try { - if (doi != null) { - String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); - IdentifierRest identifierRest = new IdentifierRest(doiUrl, "doi", String.valueOf(doi.getStatus())); - identifierRestList.add(identifierRest); - } - if (handle != null) { - identifierRestList.add(new IdentifierRest(handle, "handle", null)); - } - } catch (IdentifierException e) { - throw new IllegalStateException("Failed to register identifier: " + e.getMessage()); - } - identifiersRest.setIdentifiers(identifierRestList); - return identifiersRest; - } - /** * Queue a new, pending or minted DOI for registration for a given item. Requires administrative privilege. * This request is sent from the Register DOI button (configurable) on the item status page. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java index 7f73a6eeee31..bbcac06b1c34 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java @@ -21,8 +21,7 @@ public class IdentifiersRest implements RestModel { // Set names used in component wiring - public static final String NAME = "identifier"; - public static final String PLURAL_NAME = "identifiers"; + public static final String NAME = "identifiers"; private List identifiers; // Empty constructor diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java index 4aa73354fd25..1254ef8f9372 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -26,7 +26,8 @@ method = "getBundles" ), @LinkRest( - name = ItemRest.IDENTIFIERS + name = ItemRest.IDENTIFIERS, + method = "getIdentifiers" ), @LinkRest( name = ItemRest.MAPPED_COLLECTIONS, diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java new file mode 100644 index 000000000000..7e9576fcca8c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java @@ -0,0 +1,12 @@ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.IdentifiersRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +@RelNameDSpaceResource(IdentifiersRest.NAME) +public class IdentifiersResource extends HALResource { + public IdentifiersResource(IdentifiersRest data, Utils utils) { + super(data); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java new file mode 100644 index 000000000000..abe84834a5dc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java @@ -0,0 +1,80 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.IdentifierRest; +import org.dspace.app.rest.model.IdentifiersRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.identifier.DOI; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.service.DOIService; +import org.dspace.identifier.service.IdentifierService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for the identifier of an Item + */ +@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.IDENTIFIERS) +public class ItemIdentifierLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + @Autowired + ItemService itemService; + + @Autowired + IdentifierService identifierService; + + @Autowired + DOIService doiService; + + @PreAuthorize("hasPermission(#itemId, 'ITEM', 'READ')") + public IdentifiersRest getIdentifiers(@Nullable HttpServletRequest request, + UUID itemId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException { + Context context = ContextUtil.obtainCurrentRequestContext(); + Item item = itemService.find(context, itemId); + if (item == null) { + throw new ResourceNotFoundException("Could not find item with id " + itemId); + } + IdentifiersRest identifiersRest = new IdentifiersRest(); + List identifierRestList = new ArrayList<>(); + DOI doi = doiService.findDOIByDSpaceObject(context, item); + String handle = HandleServiceFactory.getInstance().getHandleService().findHandle(context, item); + try { + if (doi != null) { + String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); + IdentifierRest identifierRest = new IdentifierRest(doiUrl, "doi", String.valueOf(doi.getStatus())); + identifierRestList.add(identifierRest); + } + if (handle != null) { + identifierRestList.add(new IdentifierRest(handle, "handle", null)); + } + } catch (IdentifierException e) { + throw new IllegalStateException("Failed to register identifier: " + e.getMessage()); + } + identifiersRest.setIdentifiers(identifierRestList); + return identifiersRest; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java index 043d7ecf059e..579bcb95d2fa 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java @@ -112,7 +112,6 @@ public void testGetIdentifiersForItem() throws Exception { DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); - //** GIVEN ** //1. A community-collection structure with one parent community and two collections. parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -126,13 +125,18 @@ public void testGetIdentifiersForItem() throws Exception { .withAuthor("Smith, Donald") .build(); + String doiString = "10.5072/dspace-identifier-test-" + publicItem1.getID(); + // Use the DOI service to directly manipulate the DOI on this object so that we can predict and // test values via the REST request DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); - String doiString = "10.5072/dspace-identifier-test"; - doi.setDoi(doiString); - doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); - doiService.update(context, doi); + if (doi == null) { + doi.setDoi(doiString); + doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); + doiService.update(context, doi); + } + + context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java index 371ad6b4b46b..64905f90ea2d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java @@ -52,6 +52,7 @@ public static Matcher matchFullEmbeds() { return matchEmbeds( "accessStatus", "bundles[]", + "identifiers", "mappedCollections[]", "owningCollection", "version", @@ -68,6 +69,7 @@ public static Matcher matchLinks(UUID uuid) { return HalMatcher.matchLinks(REST_SERVER_URL + "core/items/" + uuid, "accessStatus", "bundles", + "identifiers", "mappedCollections", "owningCollection", "relationships", From 9a956270d3b18c150b8a528ee739b2c13dc02e34 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 31 Oct 2022 11:08:07 +1300 Subject: [PATCH 273/394] [TLC-249] Further IT fix for identifiers --- .../app/rest/ItemIdentifierControllerIT.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java index 579bcb95d2fa..30d1f4758aa2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -89,7 +90,7 @@ public void testRegisterDoiForItem() throws Exception { // Get the doi we minted and queued for registration DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); // The DOI should not be null - Assert.assertNotNull(doi); + assertNotNull(doi); // The DOI status should be TO_BE_REGISTERED Assert.assertEquals(DOIIdentifierProvider.TO_BE_REGISTERED, doi.getStatus()); @@ -130,20 +131,24 @@ public void testGetIdentifiersForItem() throws Exception { // Use the DOI service to directly manipulate the DOI on this object so that we can predict and // test values via the REST request DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); - if (doi == null) { - doi.setDoi(doiString); - doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); - doiService.update(context, doi); - } + + // Assert non-null DOI, since we should be minting them automatically here + assertNotNull(doi); + + // Set specific string and state we expect to get back from a REST request + doi.setDoi(doiString); + doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); + doiService.update(context, doi); context.restoreAuthSystemState(); String token = getAuthToken(eperson.getEmail(), password); - // Get identifiers for this item - we expect a 200 OK response and the type of identifier + // Get identifiers for this item - we expect a 200 OK response and the type of the resource is plural + // "identifiers" getClient(token).perform(get("/api/core/items/" + publicItem1.getID().toString() + "/identifiers")) - .andExpect(status().isOk()).andExpect(jsonPath("$.type").value("identifier")); + .andExpect(status().isOk()).andExpect(jsonPath("$.type").value("identifiers")); // Expect an array of identifiers getClient(token).perform(get("/api/core/items/" + From 65952537a012ed7310cb019f0439a86391a78798 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 31 Oct 2022 11:44:04 +1300 Subject: [PATCH 274/394] [TLC-249] Licence header fix --- .../app/rest/model/hateoas/IdentifiersResource.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java index 7e9576fcca8c..2c6453d8cb11 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifiersResource.java @@ -1,9 +1,21 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ package org.dspace.app.rest.model.hateoas; import org.dspace.app.rest.model.IdentifiersRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; +/** + * Boilerplate hateos resource for IdentifiersRest + * + * @author Kim Shepherd + */ @RelNameDSpaceResource(IdentifiersRest.NAME) public class IdentifiersResource extends HALResource { public IdentifiersResource(IdentifiersRest data, Utils utils) { From e82ff5eeecf6229c9494f887c768fd901495e3ce Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 19 Jan 2023 16:06:04 +1300 Subject: [PATCH 275/394] [TLC-249] Address review feedback Controller updated to be less DOI-specific in its registration implementation item-status.register renamed to registerDOI display types included in section data, for UI hints comments / javadoc added DOI status text sent instead of number in status data As per tdonohue's revew on 2022-01-18 --- .../logic/condition/IsArchivedCondition.java | 6 ++-- .../identifier/DOIIdentifierProvider.java | 15 +++++++++ .../identifier/IdentifierServiceImpl.java | 20 +++++------ .../app/rest/ItemIdentifierController.java | 33 ++++++++++++++----- .../app/rest/model/step/DataIdentifiers.java | 10 ++++++ .../ItemIdentifierLinkRepository.java | 4 ++- .../rest/submit/step/ShowIdentifiersStep.java | 8 ++++- .../app/rest/ItemIdentifierControllerIT.java | 6 ++-- dspace/config/item-submission.xml | 4 ++- dspace/config/modules/identifiers.cfg | 11 +++++-- dspace/config/modules/rest.cfg | 2 +- 11 files changed, 86 insertions(+), 33 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java index 4ae1c7dc9139..95e4b05523f2 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java @@ -14,7 +14,7 @@ import org.dspace.core.Context; /** - * A condition that returns true if the item is withdrawn + * A condition that returns true if the item is archived * * @author Kim Shepherd * @version $Revision$ @@ -23,7 +23,7 @@ public class IsArchivedCondition extends AbstractCondition { private final static Logger log = LogManager.getLogger(); /** - * Return true if item is withdrawn + * Return true if item is archived * Return false if not * @param context DSpace context * @param item Item to evaluate @@ -32,7 +32,7 @@ public class IsArchivedCondition extends AbstractCondition { */ @Override public boolean getResult(Context context, Item item) throws LogicalStatementException { - log.debug("Result of isWithdrawn is " + item.isArchived()); + log.debug("Result of isArchived is " + item.isArchived()); return item.isArchived(); } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java index f5008d575e25..533da2e34b69 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -97,6 +97,21 @@ public class DOIIdentifierProvider extends FilteredIdentifierProvider { // The DOI is created in the database, but no more context is known public static final Integer MINTED = 11; + public static final String[] statusText = { + "UNKNOWN", // 0 + "TO_BE_REGISTERED", // 1 + "TO_BE_RESERVED", // 2 + "IS_REGISTERED", // 3 + "IS_RESERVED", // 4 + "UPDATE_RESERVED", // 5 + "UPDATE_REGISTERED", // 6 + "UPDATE_BEFORE_REGISTRATION", // 7 + "TO_BE_DELETED", // 8 + "DELETED", // 9 + "PENDING", // 10 + "MINTED", // 11 + }; + @Autowired(required = true) protected DOIService doiService; @Autowired(required = true) diff --git a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java index bf91bded1ab9..15e0e1c1ec13 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -117,9 +117,8 @@ public void register(Context context, DSpaceObject dso) @Override public void register(Context context, DSpaceObject dso, Class type, Filter filter) throws AuthorizeException, SQLException, IdentifierException { - //We need to commit our context because one of the providers might require the handle created above - // Next resolve all other services boolean registered = false; + // Iterate all services and register identifiers as appropriate for (IdentifierProvider service : providers) { if (service.supports(type)) { try { @@ -139,16 +138,15 @@ public void register(Context context, DSpaceObject dso, Class type) throws AuthorizeException, SQLException, IdentifierException { - //We need to commit our context because one of the providers might require the handle created above - // Next resolve all other services boolean registered = false; + // Iterate all services and register identifiers as appropriate for (IdentifierProvider service : providers) { if (service.supports(type)) { try { @@ -163,15 +161,14 @@ public void register(Context context, DSpaceObject dso, Class, Filter> typeFilters) throws AuthorizeException, SQLException, IdentifierException { - //We need to commit our context because one of the providers might require the handle created above - // Next resolve all other services + // Iterate all services and register identifiers as appropriate for (IdentifierProvider service : providers) { try { // If the service supports filtering, look through the map and the first supported class @@ -200,7 +197,7 @@ public void register(Context context, DSpaceObject dso, Map> registerIdentifierForItem(@PathVariable UUID uuid, HttpServletRequest request, - HttpServletResponse response) + HttpServletResponse response, + @RequestParam(name = "type") String type) throws SQLException, AuthorizeException { Context context = ContextUtil.obtainContext(request); Item item = itemService.find(context, uuid); - ItemRest itemRest; + if (item == null) { throw new ResourceNotFoundException("Could not find item with id " + uuid); } + // Check for a valid identifier type and register the appropriate type of identifier + if ("doi".equals(type)) { + return registerDOI(context, item); + } else { + throw new IllegalArgumentException("Valid identifier type (eg. 'doi') is required, parameter name 'type'"); + } + } + + /** + * Queue a new, pending or minted DOI for registration for a given item. Requires administrative privilege. + * This request is sent from the Register DOI button (configurable) on the item status page. + * + * @return 302 FOUND if the DOI is already registered or reserved, 201 CREATED if queued for registration + */ + private ResponseEntity> registerDOI(Context context, Item item) + throws SQLException, AuthorizeException { String identifier = null; HttpStatus httpStatus = HttpStatus.BAD_REQUEST; + ItemRest itemRest; try { DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); @@ -148,5 +164,4 @@ public ResponseEntity> registerIdentifierForItem(@PathVar // Return the status and item resource return ControllerUtils.toResponseEntity(httpStatus, new HttpHeaders(), itemResource); } - } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java index 7e9097037789..1f5a92f53ded 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java @@ -19,6 +19,8 @@ public class DataIdentifiers implements SectionData { String handle; String doi; List otherIdentifiers; + // Types to display, a hint for te UI + List displayTypes; public DataIdentifiers() { @@ -48,6 +50,14 @@ public void setOtherIdentifiers(List otherIdentifiers) { this.otherIdentifiers = otherIdentifiers; } + public List getDisplayTypes() { + return displayTypes; + } + + public void setDisplayTypes(List displayTypes) { + this.displayTypes = displayTypes; + } + public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Handle: ").append(handle); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java index abe84834a5dc..83e7b808ba99 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java @@ -25,6 +25,7 @@ import org.dspace.core.Context; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.service.DOIService; import org.dspace.identifier.service.IdentifierService; @@ -65,7 +66,8 @@ public IdentifiersRest getIdentifiers(@Nullable HttpServletRequest request, try { if (doi != null) { String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); - IdentifierRest identifierRest = new IdentifierRest(doiUrl, "doi", String.valueOf(doi.getStatus())); + IdentifierRest identifierRest = new IdentifierRest( + doiUrl, "doi", DOIIdentifierProvider.statusText[doi.getStatus()]); identifierRestList.add(identifierRest); } if (handle != null) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java index 173ac4c500fe..c0b2e965b52b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -91,6 +92,11 @@ private DataIdentifiers getIdentifierData(InProgressSubmission obj) { IdentifierService identifierService = IdentifierServiceFactory.getInstance().getIdentifierService(); // Attempt to look up handle and DOI identifiers for this item + String[] defaultTypes = {"handle", "doi"}; + List displayTypes = Arrays.asList(configurationService.getArrayProperty( + "identifiers.submission.display", + defaultTypes)); + result.setDisplayTypes(displayTypes); String handle = identifierService.lookup(context, obj.getItem(), Handle.class); String simpleDoi = identifierService.lookup(context, obj.getItem(), DOI.class); DOI doi = null; @@ -126,7 +132,7 @@ private DataIdentifiers getIdentifierData(InProgressSubmission obj) { handle = HandleServiceFactory.getInstance().getHandleService().getCanonicalForm(handle); } - // Populate bean with data and return + // Populate bean with data and return, if the identifier type is configured for exposure result.setDoi(doiString); result.setHandle(handle); result.setOtherIdentifiers(otherIdentifiers); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java index 30d1f4758aa2..624b77b867e9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java @@ -71,7 +71,7 @@ public void testRegisterDoiForItem() throws Exception { // Expect first forbidden String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) + publicItem1.getID().toString() + "/identifiers?type=doi")) .andExpect(status().isForbidden()); // Set token to admin credentials @@ -79,12 +79,12 @@ public void testRegisterDoiForItem() throws Exception { // Expect a successful 201 CREATED for this item with no DOI getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) + publicItem1.getID().toString() + "/identifiers?type=doi")) .andExpect(status().isCreated()); // Expected 302 FOUND status code for a DOI already in REGISTERED / TO_BE_REGISTERED state getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) + publicItem1.getID().toString() + "/identifiers?type=doi")) .andExpect(status().isFound()); // Get the doi we minted and queued for registration diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 2aa1646bfa54..2ab26dcf57df 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -240,7 +240,9 @@ - + + diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg index 8280a7e06021..c9bade57b342 100644 --- a/dspace/config/modules/identifiers.cfg +++ b/dspace/config/modules/identifiers.cfg @@ -5,7 +5,8 @@ # as the Show Identifiers step which can "pre-mint" DOIs and Handles # #----------------------------------------------------------------------# -# Should DOIs be minted for (future) registration at workspace item creation? +# Should configured identifiers (eg handle and DOI) be minted for (future) registration at workspace item creation? +# A handle created at this stage will act just like a regular handle created at archive time. # A DOI created at this stage will be in a 'PENDING' status while in workspace and workflow. # At the time of item install, the DOI filter (if any) will be applied and if the item matches the filter, the DOI # status will be updated to TO_BE_REGISTERED. An administrator can also manually progress the DOI status, overriding @@ -40,4 +41,10 @@ # Show Register DOI button in item status page? # Default: false -#identifiers.item-status.register = true \ No newline at end of file +#identifiers.item-status.registerDOI = true + +# Which identifier types to show in submission step? +# Default: handle, doi, other (this is all the current supported identifier 'types') +#identifiers.submission.display = handle +#identifiers.submission.display = doi +#identifiers.submission.display = other \ No newline at end of file diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 754e7b1e262f..6421258c575b 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -46,7 +46,7 @@ rest.properties.exposed = google.recaptcha.key.site rest.properties.exposed = google.recaptcha.version rest.properties.exposed = google.recaptcha.mode rest.properties.exposed = cc.license.jurisdiction -rest.properties.exposed = identifiers.item-status.register +rest.properties.exposed = identifiers.item-status.register-doi rest.properties.exposed = authentication-password.domain.valid #---------------------------------------------------------------# From 6737a218f6f586c72eb461fd65b59c4876343743 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 19 Jan 2023 16:24:19 +1300 Subject: [PATCH 276/394] [TLC-249] Lint fixes --- .../main/java/org/dspace/app/rest/ItemIdentifierController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java index 4bb901255366..693d5013f6b8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java @@ -27,7 +27,6 @@ import org.dspace.content.logic.TrueFilter; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.identifier.DOI; import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; From d376f45e969eb4266ab2557d5f65cb69c60d9d55 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Jan 2023 13:09:31 +1300 Subject: [PATCH 277/394] [TLC-249] Improve model for identifier step data --- .../dspace/app/rest/model/IdentifierRest.java | 36 +++++++++- .../app/rest/model/IdentifiersRest.java | 1 + .../app/rest/model/step/DataIdentifiers.java | 70 ++++++++++++------- .../rest/submit/step/ShowIdentifiersStep.java | 19 ++--- 4 files changed, 86 insertions(+), 40 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java index e6de995eb338..31d76fad1a96 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -28,39 +28,73 @@ public class IdentifierRest implements RestModel { public IdentifierRest() { } + /** + * Constructor that takes a value, type and status for an identifier + * @param value the identifier value eg. https://doi.org/123/234 + * @param identifierType identifier type eg. doi + * @param identifierStatus identifier status eg. TO_BE_REGISTERED + */ public IdentifierRest(String value, String identifierType, String identifierStatus) { this.value = value; this.identifierType = identifierType; this.identifierStatus = identifierStatus; } - // Return name for getType() + /** + * Return name for getType() - this is the section name + * and not the type of identifier, see: identifierType string + * @return + */ @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getType() { return NAME; } + /** + * Get the identifier value eg full DOI URL + * @return identifier value eg. https://doi.org/123/234 + */ public String getValue() { return value; } + /** + * Set the identifier value + * @param value identifier value, eg. https://doi.org/123/234 + */ public void setValue(String value) { this.value = value; } + /** + * Get type of identifier eg 'doi' or 'handle' + * @return type string + */ public String getIdentifierType() { return identifierType; } + /** + * Set type of identifier + * @param identifierType type string eg 'doi' + */ public void setIdentifierType(String identifierType) { this.identifierType = identifierType; } + /** + * Get status of identifier, if relevant + * @return identifierStatus eg. null or TO_BE_REGISTERED + */ public String getIdentifierStatus() { return identifierStatus; } + /** + * Set status of identifier, if relevant + * @param identifierStatus eg. null or TO_BE_REGISTERED + */ public void setIdentifierStatus(String identifierStatus) { this.identifierStatus = identifierStatus; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java index bbcac06b1c34..739f2004f81f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java @@ -30,6 +30,7 @@ public IdentifiersRest() { } // Return name for getType() + // Note this is the section name, NOT the identifier type @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getType() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java index 1f5a92f53ded..e904d9d6d740 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java @@ -7,8 +7,10 @@ */ package org.dspace.app.rest.model.step; +import java.util.ArrayList; import java.util.List; +import org.dspace.app.rest.model.IdentifierRest; /** * Java bean with basic DOI / Handle / other identifier data for * display in submission step @@ -16,38 +18,30 @@ * @author Kim Shepherd (kim@shepherd.nz) */ public class DataIdentifiers implements SectionData { - String handle; - String doi; - List otherIdentifiers; + // Map of identifier types and values + List identifiers; // Types to display, a hint for te UI List displayTypes; public DataIdentifiers() { - - } - - public String getHandle() { - return handle; - } - - public void setHandle(String handle) { - this.handle = handle; - } - - public String getDoi() { - return doi; + identifiers = new ArrayList<>(); + displayTypes = new ArrayList<>(); } - public void setDoi(String doi) { - this.doi = doi; + public List getIdentifiers() { + return identifiers; } - public List getOtherIdentifiers() { - return otherIdentifiers; + public void setIdentifiers(List identifiers) { + this.identifiers = identifiers; } - public void setOtherIdentifiers(List otherIdentifiers) { - this.otherIdentifiers = otherIdentifiers; + public void addIdentifier(String type, String value, String status) { + IdentifierRest identifier = new IdentifierRest(); + identifier.setValue(value); + identifier.setIdentifierType(type); + identifier.setIdentifierStatus(status); + this.identifiers.add(identifier); } public List getDisplayTypes() { @@ -60,10 +54,36 @@ public void setDisplayTypes(List displayTypes) { public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Handle: ").append(handle); - sb.append("DOI: ").append(doi); - sb.append("Others: ").append(String.join(", ", otherIdentifiers)); + for (IdentifierRest identifier : identifiers) { + sb.append(identifier.getType()).append(": ").append(identifier.getValue()).append("\n"); + } return sb.toString(); } + protected class DataIdentifier { + private String type; + private String value; + + public DataIdentifier(String type, String value) { + this.type = type; + this.value = value; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java index c0b2e965b52b..df163eb9252d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/ShowIdentifiersStep.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.submit.step; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -85,7 +84,6 @@ public DataIdentifiers getData(SubmissionService submissionService, InProgressSu * @return A simple DataIdentifiers bean containing doi, handle and list of other identifiers */ private DataIdentifiers getIdentifierData(InProgressSubmission obj) { - log.debug("getIdentifierData() called"); Context context = getContext(); DataIdentifiers result = new DataIdentifiers(); // Load identifier service @@ -98,7 +96,6 @@ private DataIdentifiers getIdentifierData(InProgressSubmission obj) { defaultTypes)); result.setDisplayTypes(displayTypes); String handle = identifierService.lookup(context, obj.getItem(), Handle.class); - String simpleDoi = identifierService.lookup(context, obj.getItem(), DOI.class); DOI doi = null; String doiString = null; try { @@ -111,13 +108,8 @@ private DataIdentifiers getIdentifierData(InProgressSubmission obj) { log.error(e.getMessage()); } - // Look up all identifiers and if they're not the DOI or handle, add them to the 'other' list - List otherIdentifiers = new ArrayList<>(); - for (String identifier : identifierService.lookup(context, obj.getItem())) { - if (!StringUtils.equals(simpleDoi, identifier) && !StringUtils.equals(handle, identifier)) { - otherIdentifiers.add(identifier); - } - } + // Other identifiers can be looked up / resolved through identifier service or + // its own specific service here // If we got a DOI, format it to its external form if (StringUtils.isNotEmpty(doiString)) { @@ -133,10 +125,9 @@ private DataIdentifiers getIdentifierData(InProgressSubmission obj) { } // Populate bean with data and return, if the identifier type is configured for exposure - result.setDoi(doiString); - result.setHandle(handle); - result.setOtherIdentifiers(otherIdentifiers); - + result.addIdentifier("doi", doiString, + doi != null ? DOIIdentifierProvider.statusText[doi.getStatus()] : null); + result.addIdentifier("handle", handle, null); return result; } From 9c7729e0ab58fc8383b6e9fe664dba202119407e Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Jan 2023 14:25:36 +1300 Subject: [PATCH 278/394] [TLC-249] Update configuration identifiers.cfg --- dspace/config/modules/identifiers.cfg | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg index c9bade57b342..ba73880139be 100644 --- a/dspace/config/modules/identifiers.cfg +++ b/dspace/config/modules/identifiers.cfg @@ -44,7 +44,6 @@ #identifiers.item-status.registerDOI = true # Which identifier types to show in submission step? -# Default: handle, doi, other (this is all the current supported identifier 'types') +# Default: handle, doi (currently the only supported identifier 'types') #identifiers.submission.display = handle -#identifiers.submission.display = doi -#identifiers.submission.display = other \ No newline at end of file +#identifiers.submission.display = doi \ No newline at end of file From dbcd1b9525c83f788994f188c0049ee6b4d31eb0 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Jan 2023 14:27:19 +1300 Subject: [PATCH 279/394] [TLC-249] Remove unused subclass DataIdentifier --- .../app/rest/model/step/DataIdentifiers.java | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java index e904d9d6d740..01e0eabdd380 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataIdentifiers.java @@ -60,30 +60,4 @@ public String toString() { return sb.toString(); } - protected class DataIdentifier { - private String type; - private String value; - - public DataIdentifier(String type, String value) { - this.type = type; - this.value = value; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - } - } From 94f7be07276db74f5ab7a0d48c60906ca973520c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Jan 2023 16:04:43 +1300 Subject: [PATCH 280/394] [TLC-249] Update identifier section IT --- .../app/rest/SubmissionShowIdentifiersRestIT.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java index 2d0241b35760..b879afec9c37 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java @@ -31,7 +31,7 @@ /** * Test suite for testing the Show Identifiers submission step - * + * * @author Kim Shepherd * */ @@ -96,7 +96,9 @@ public void testItemHandleReservation() throws Exception { String submitterToken = getAuthToken(submitter.getEmail(), password); getClient(submitterToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.identifiers.handle").exists()); + .andExpect(jsonPath("$.sections.identifiers.handle").exists()) + .andExpect(jsonPath("$.sections.identifiers[1].type").value("identifier")) + .andExpect(jsonPath("$.sections.identifiers[1].identifierType").value("handle")); } @Test @@ -109,7 +111,9 @@ public void testItemDoiReservation() throws Exception { String submitterToken = getAuthToken(submitter.getEmail(), password); getClient(submitterToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.identifiers.doi").exists()); + .andExpect(jsonPath("$.sections.identifiers.handle").exists()) + .andExpect(jsonPath("$.sections.identifiers[0].type").value("identifier")) + .andExpect(jsonPath("$.sections.identifiers[0].identifierType").value("doi")); } private WorkspaceItem createWorkspaceItem(String title, Collection collection) { From 7dafb1ec43aab77dbba1d2cc1195db134167effd Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 26 Jan 2023 13:30:22 +1300 Subject: [PATCH 281/394] [TLC-249] Update integration tests for identifier controller --- .../org/dspace/app/rest/ItemIdentifierControllerIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java index 624b77b867e9..8f08ddcf5051 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java @@ -71,7 +71,7 @@ public void testRegisterDoiForItem() throws Exception { // Expect first forbidden String token = getAuthToken(eperson.getEmail(), password); getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers?type=doi")) + publicItem1.getID().toString() + "/identifiers?type=doi").content("doi")) .andExpect(status().isForbidden()); // Set token to admin credentials @@ -79,7 +79,7 @@ public void testRegisterDoiForItem() throws Exception { // Expect a successful 201 CREATED for this item with no DOI getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers?type=doi")) + publicItem1.getID().toString() + "/identifiers?type=doi").content("hello")) .andExpect(status().isCreated()); // Expected 302 FOUND status code for a DOI already in REGISTERED / TO_BE_REGISTERED state @@ -100,7 +100,7 @@ public void testRegisterDoiForItem() throws Exception { // Do another POST, again this should return 201 CREATED as we shift the DOI from PENDING to TO_BE_REGISTERED getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) + publicItem1.getID().toString() + "/identifiers?type=doi")) .andExpect(status().isCreated()); context.restoreAuthSystemState(); @@ -164,7 +164,7 @@ public void testGetIdentifiersForItem() throws Exception { .andExpect(jsonPath("$.identifiers[0].value").value(doiService.DOIToExternalForm(doiString))) .andExpect(jsonPath("$.identifiers[0].identifierType").value("doi")) .andExpect(jsonPath("$.identifiers[0].identifierStatus") - .value(DOIIdentifierProvider.IS_REGISTERED.toString())); + .value(DOIIdentifierProvider.statusText[DOIIdentifierProvider.IS_REGISTERED])); // Expect a valid Handle with the value, type we expect getClient(token).perform(get("/api/core/items/" + From 243df364b5bf42d475f309d6072ff27de02cd8b9 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 26 Jan 2023 15:16:03 +1300 Subject: [PATCH 282/394] [TLC-249] Fix submission controller IT --- .../rest/SubmissionShowIdentifiersRestIT.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java index b879afec9c37..8d95f4627b72 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java @@ -24,6 +24,8 @@ import org.dspace.content.WorkspaceItem; import org.dspace.content.service.WorkspaceItemService; import org.dspace.eperson.EPerson; +import org.dspace.handle.service.HandleService; +import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.services.ConfigurationService; import org.junit.After; import org.junit.Test; @@ -43,6 +45,9 @@ public class SubmissionShowIdentifiersRestIT extends AbstractControllerIntegrati @Autowired private ConfigurationService configurationService; + @Autowired + private HandleService handleService; + private Collection collection; private EPerson submitter; @@ -93,12 +98,14 @@ public void testItemHandleReservation() throws Exception { context.turnOffAuthorisationSystem(); WorkspaceItem workspaceItem = createWorkspaceItem("Test publication", collection); context.restoreAuthSystemState(); + // Expected handle + String expectedHandle = handleService.resolveToURL(context, workspaceItem.getItem().getHandle()); String submitterToken = getAuthToken(submitter.getEmail(), password); getClient(submitterToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.identifiers.handle").exists()) - .andExpect(jsonPath("$.sections.identifiers[1].type").value("identifier")) - .andExpect(jsonPath("$.sections.identifiers[1].identifierType").value("handle")); + .andExpect(jsonPath("$.sections.identifiers.identifiers[1].type").value("identifier")) + .andExpect(jsonPath("$.sections.identifiers.identifiers[1].value").value(expectedHandle)) + .andExpect(jsonPath("$.sections.identifiers.identifiers[1].identifierType").value("handle")); } @Test @@ -110,10 +117,11 @@ public void testItemDoiReservation() throws Exception { String submitterToken = getAuthToken(submitter.getEmail(), password); getClient(submitterToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.sections.identifiers.handle").exists()) - .andExpect(jsonPath("$.sections.identifiers[0].type").value("identifier")) - .andExpect(jsonPath("$.sections.identifiers[0].identifierType").value("doi")); + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.identifiers.identifiers[0].type").value("identifier")) + .andExpect(jsonPath("$.sections.identifiers.identifiers[0].identifierType").value("doi")) + .andExpect(jsonPath("$.sections.identifiers.identifiers[0].identifierStatus") + .value(DOIIdentifierProvider.statusText[DOIIdentifierProvider.PENDING])); } private WorkspaceItem createWorkspaceItem(String title, Collection collection) { From 0f77f1d3b16ab43c6dd51011b5883183a101a9bc Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 31 Jan 2023 14:13:53 +1300 Subject: [PATCH 283/394] [TLC-249] Update canRegisterDOIFeature to check for cfg enabled --- .../rest/authorization/impl/CanRegisterDOIFeature.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java index a563c8ec8f24..df2b4d8f5c74 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java @@ -6,6 +6,7 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest.authorization.impl; +import javax.ws.rs.NotAllowedException; import java.sql.SQLException; import org.dspace.app.rest.authorization.AuthorizationFeature; @@ -15,6 +16,7 @@ import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.security.DSpaceRestPermission; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -30,12 +32,18 @@ public class CanRegisterDOIFeature implements AuthorizationFeature { @Autowired private AuthorizeServiceRestUtil authorizeServiceRestUtil; + @Autowired + private ConfigurationService configurationService; public static final String NAME = "canRegisterDOI"; @Override @SuppressWarnings("rawtypes") public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException { + // Check configuration to see if this REST operation is allowed + if (!configurationService.getBooleanProperty("identifiers.item-status.register-doi", false)) { + return false; + } if (object instanceof ItemRest) { return authorizeServiceRestUtil.authorizeActionBoolean(context, object, DSpaceRestPermission.ADMIN); } From f9ffceb172cf36b39709a4c1c660889ca864d5a7 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 1 Feb 2023 16:26:03 +1300 Subject: [PATCH 284/394] [TLC-249] Refactor identifiers create endpoint --- .../app/rest/IdentifierRestController.java | 2 +- .../app/rest/ItemIdentifierController.java | 166 ------------- .../app/rest/RestResourceController.java | 1 + .../dspace/app/rest/model/IdentifierRest.java | 18 +- .../app/rest/model/IdentifiersRest.java | 12 +- .../model/hateoas/IdentifierResource.java | 25 ++ .../repository/IdentifierRestRepository.java | 223 ++++++++++++++++++ dspace/config/modules/identifiers.cfg | 2 +- 8 files changed, 279 insertions(+), 170 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifierResource.java create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java index 5f7a473b910f..35b21ecc3dfb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java @@ -44,7 +44,7 @@ @RestController @RequestMapping("/api/" + IdentifierRestController.CATEGORY) public class IdentifierRestController implements InitializingBean { - public static final String CATEGORY = "pid"; + public static final String CATEGORY = "pid_SDFSDFSDFSDF"; public static final String ACTION = "find"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java deleted file mode 100644 index 693d5013f6b8..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemIdentifierController.java +++ /dev/null @@ -1,166 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest; - -import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; - -import java.sql.SQLException; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.converter.MetadataConverter; -import org.dspace.app.rest.model.IdentifiersRest; -import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.model.hateoas.ItemResource; -import org.dspace.app.rest.repository.ItemRestRepository; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.app.rest.utils.Utils; -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.Item; -import org.dspace.content.logic.TrueFilter; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.dspace.identifier.DOI; -import org.dspace.identifier.DOIIdentifierProvider; -import org.dspace.identifier.IdentifierException; -import org.dspace.identifier.IdentifierNotFoundException; -import org.dspace.identifier.service.DOIService; -import org.dspace.identifier.service.IdentifierService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.rest.webmvc.ControllerUtils; -import org.springframework.data.rest.webmvc.ResourceNotFoundException; -import org.springframework.hateoas.RepresentationModel; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -/** - * Controller to register a DOI for an item, if it has no DOI already, or a DOI in a state where it can be - * advanced to queue for reservation or registration. - * - * @author Kim Shepherd - */ -@RestController -@RequestMapping("/api/" + ItemRest.CATEGORY + "/" + ItemRest.PLURAL_NAME + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID - + "/" + IdentifiersRest.NAME) -public class ItemIdentifierController { - - @Autowired - ConverterService converter; - - @Autowired - ItemService itemService; - - @Autowired - ItemRestRepository itemRestRepository; - - @Autowired - MetadataConverter metadataConverter; - - @Autowired - IdentifierService identifierService; - - @Autowired - DOIService doiService; - - @Autowired - Utils utils; - - /** - * Request that an identifier of a given type is 'created' for an item. Depending on the identifier type - * this could mean minting, registration, reservation, queuing for registration later, etc. - * - * @return 201 CREATED on success, or 302 FOUND if already created, or an error - */ - @RequestMapping(method = RequestMethod.POST) - @PreAuthorize("hasPermission(#uuid, 'ITEM', 'ADMIN')") - public ResponseEntity> registerIdentifierForItem(@PathVariable UUID uuid, - HttpServletRequest request, - HttpServletResponse response, - @RequestParam(name = "type") String type) - throws SQLException, AuthorizeException { - Context context = ContextUtil.obtainContext(request); - - Item item = itemService.find(context, uuid); - - - if (item == null) { - throw new ResourceNotFoundException("Could not find item with id " + uuid); - } - - // Check for a valid identifier type and register the appropriate type of identifier - if ("doi".equals(type)) { - return registerDOI(context, item); - } else { - throw new IllegalArgumentException("Valid identifier type (eg. 'doi') is required, parameter name 'type'"); - } - } - - /** - * Queue a new, pending or minted DOI for registration for a given item. Requires administrative privilege. - * This request is sent from the Register DOI button (configurable) on the item status page. - * - * @return 302 FOUND if the DOI is already registered or reserved, 201 CREATED if queued for registration - */ - private ResponseEntity> registerDOI(Context context, Item item) - throws SQLException, AuthorizeException { - String identifier = null; - HttpStatus httpStatus = HttpStatus.BAD_REQUEST; - ItemRest itemRest; - try { - DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() - .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); - if (doiIdentifierProvider != null) { - DOI doi = doiService.findDOIByDSpaceObject(context, item); - boolean exists = false; - boolean pending = false; - if (null != doi) { - exists = true; - Integer doiStatus = doiService.findDOIByDSpaceObject(context, item).getStatus(); - // Check if this DOI has a status which makes it eligible for registration - if (null == doiStatus || DOIIdentifierProvider.MINTED.equals(doiStatus) - || DOIIdentifierProvider.PENDING.equals(doiStatus)) { - pending = true; - } - } - if (!exists || pending) { - // Mint identifier and return 201 CREATED - doiIdentifierProvider.register(context, item, new TrueFilter()); - httpStatus = HttpStatus.CREATED; - } else { - // This DOI exists and isn't in a state where it can be queued for registration - // We'll return 302 FOUND to indicate it's here and not an error, but no creation was performed - httpStatus = HttpStatus.FOUND; - } - } else { - throw new IllegalStateException("No DOI provider is configured"); - } - } catch (IdentifierNotFoundException e) { - httpStatus = HttpStatus.NOT_FOUND; - } catch (IdentifierException e) { - throw new IllegalStateException("Failed to register identifier: " + identifier); - } - // We didn't exactly change the item, but we did queue an identifier which is closely associated with it - // so we should update the last modified date here - itemService.updateLastModified(context, item); - itemRest = converter.toRest(item, utils.obtainProjection()); - context.complete(); - ItemResource itemResource = converter.toResource(itemRest); - // Return the status and item resource - return ControllerUtils.toResponseEntity(httpStatus, new HttpHeaders(), itemResource); - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index b82b4830753c..167b111f82c1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -125,6 +125,7 @@ public void afterPropertiesSet() { // this doesn't work as we don't have an active http request // see https://github.com/spring-projects/spring-hateoas/issues/408 // Link l = linkTo(this.getClass(), r).withRel(r); + log.error(r); String[] split = r.split("\\.", 2); String plural = English.plural(split[1]); Link l = Link.of("/api/" + split[0] + "/" + plural, plural); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java index 31d76fad1a96..8dc9f22d6893 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.model; import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; /** * Implementation of IdentifierRest REST resource, representing some DSpace identifier @@ -15,7 +16,7 @@ * * @author Kim Shepherd */ -public class IdentifierRest implements RestModel { +public class IdentifierRest extends BaseObjectRest implements RestModel { // Set names used in component wiring public static final String NAME = "identifier"; @@ -51,6 +52,11 @@ public String getType() { return NAME; } + @Override + public String getTypePlural() { + return PLURAL_NAME; + } + /** * Get the identifier value eg full DOI URL * @return identifier value eg. https://doi.org/123/234 @@ -98,4 +104,14 @@ public String getIdentifierStatus() { public void setIdentifierStatus(String identifierStatus) { this.identifierStatus = identifierStatus; } + + @Override + public String getCategory() { + return "ppid"; + } + + @Override + public Class getController() { + return RestResourceController.class; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java index 739f2004f81f..169e40979c98 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifiersRest.java @@ -18,7 +18,7 @@ * * @author Kim Shepherd */ -public class IdentifiersRest implements RestModel { +public class IdentifiersRest extends BaseObjectRest { // Set names used in component wiring public static final String NAME = "identifiers"; @@ -44,4 +44,14 @@ public List getIdentifiers() { public void setIdentifiers(List identifiers) { this.identifiers = identifiers; } + + @Override + public String getCategory() { + return null; + } + + @Override + public Class getController() { + return null; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifierResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifierResource.java new file mode 100644 index 000000000000..e25f04f6c91c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/IdentifierResource.java @@ -0,0 +1,25 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.hateoas; + +import org.dspace.app.rest.model.IdentifierRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * + * Simple HAL wrapper for IdentifierRest model + * + * @author Kim Shepherd + */ +@RelNameDSpaceResource(IdentifierRest.NAME) +public class IdentifierResource extends DSpaceResource { + public IdentifierResource(IdentifierRest model, Utils utils) { + super(model, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java new file mode 100644 index 000000000000..8f8c88a7e03d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java @@ -0,0 +1,223 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.NotSupportedException; + +import org.dspace.app.rest.DiscoverableEndpointsService; +import org.dspace.app.rest.IdentifierRestController; +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.LinkNotFoundException; +import org.dspace.app.rest.exception.RESTAuthorizationException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.IdentifierRest; +import org.dspace.app.rest.repository.handler.service.UriListHandlerService; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.logic.TrueFilter; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.handle.service.HandleService; +import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.service.DOIService; +import org.dspace.identifier.service.IdentifierService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Controller for exposition of vocabularies entry details for the submission + * + * @author Andrea Bollini (andrea.bollini at 4science.it) + */ +@Component("pid.identifier") +public class IdentifierRestRepository extends DSpaceRestRepository { + @Autowired + private DiscoverableEndpointsService discoverableEndpointsService; + @Autowired + private UriListHandlerService uriListHandlerService; + @Autowired + private DOIService doiService; + @Autowired + private HandleService handleService; + @Autowired + private ItemService itemService; + @Autowired + private IdentifierService identifierService; + + @PreAuthorize("permitAll()") + @Override + public Page findAll(Context context, Pageable pageable) { + List results = new ArrayList<>(); + //return converter.toRestPage(results, pageable, utils.obtainProjection()); + return new PageImpl<>(results, pageable, 0); + } + + @PreAuthorize("permitAll()") + @Override + public IdentifierRest findOne(Context context, String identifier) { + DSpaceObject dso; + IdentifierRest identifierRest = new IdentifierRest(); + try { + // Resolve to an object first - if that fails then this is not a valid identifier anyway. + dso = identifierService.resolve(context, identifier); + if (dso != null) { + // DSpace has no concept of a higher-level Identifier object, so in order to detect the type + // and return sufficient information, we have to try the identifier types we know are currently + // supported. + // First, try to resolve to a handle. + dso = handleService.resolveToObject(context, identifier); + if (dso == null) { + // No object found for a handle, try DOI + DOI doi = doiService.findByDoi(context, identifier); + if (doi != null) { + String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); + identifierRest.setIdentifierType("doi"); + identifierRest.setIdentifierStatus(DOIIdentifierProvider.statusText[doi.getStatus()]); + identifierRest.setValue(doiUrl); + } + } else { + // Handle found + identifierRest.setIdentifierType("handle"); + identifierRest.setIdentifierStatus(null); + identifierRest.setValue(handleService.getCanonicalForm(dso.getHandle())); + } + } else { + throw new LinkNotFoundException(IdentifierRestController.CATEGORY, IdentifierRest.NAME, identifier); + } + } catch (SQLException | IdentifierException e) { + throw new LinkNotFoundException(IdentifierRestController.CATEGORY, IdentifierRest.NAME, identifier); + } + return identifierRest; + } + + @SearchRestMethod(name = "findByItem") + @PreAuthorize("permitAll()") + public Page findByItem(@Parameter(value = "uuid", required = true) + String uuid, Pageable pageable) { + Context context = obtainContext(); + List results = new ArrayList<>(); + try { + DSpaceObject dso = itemService.find(context, UUID.fromString(uuid)); + String handle = dso.getHandle(); + DOI doi = doiService.findDOIByDSpaceObject(context, dso); + if (doi != null) { + String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); + results.add(new IdentifierRest(doiUrl, "doi", DOIIdentifierProvider.statusText[doi.getStatus()])); + } + if (handle != null) { + String handleUrl = handleService.getCanonicalForm(handle); + results.add(new IdentifierRest(handleUrl, "handle", null)); + } + } catch (SQLException | IdentifierException e) { + throw new LinkNotFoundException(IdentifierRestController.CATEGORY, IdentifierRest.NAME, uuid); + } + // Return list of identifiers for this DSpaceObject + return new PageImpl<>(results, pageable, results.size()); + } + + /** + * Right now, the only supported identifier type is DOI + * @param context + * the dspace context + * @param list + * The list of Strings that will be used as data for the object that's to be created + * This list is retrieved from the uri-list body + * @return + * @throws AuthorizeException + * @throws SQLException + * @throws RepositoryMethodNotImplementedException + */ + @Override + protected IdentifierRest createAndReturn(Context context, List list) + throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { + HttpServletRequest request = getRequestService().getCurrentRequest().getHttpServletRequest(); + // Extract 'type' from request + String type = request.getParameter("type"); + if (!"doi".equals(type)) { + throw new NotSupportedException("Only identifiers of type 'doi' are supported"); + } + IdentifierRest identifierRest = new IdentifierRest(); + try { + Item item = uriListHandlerService.handle(context, request, list, Item.class); + if (item == null) { + throw new UnprocessableEntityException( + "No DSpace Item found, the uri-list does not contain a valid resource"); + } + // Does this item have a DOI already? If the DOI doesn't exist or has a null, MINTED or PENDING status + // then we proceed with a typical create operation and return 201 success with the object + DOI doi = doiService.findDOIByDSpaceObject(context, item); + if (doi == null || null == doi.getStatus() || DOIIdentifierProvider.MINTED.equals(doi.getStatus()) + || DOIIdentifierProvider.PENDING.equals(doi.getStatus())) { + // Proceed with creation + // Register things + identifierRest = registerDOI(context, item); + } else { + // Nothing to do here, return existing DOI + identifierRest = new IdentifierRest(doiService.DOIToExternalForm(doi.getDoi()), + "doi", DOIIdentifierProvider.statusText[doi.getStatus()]); + } + } catch (AuthorizeException e) { + throw new RESTAuthorizationException(e); + } catch (IdentifierException e) { + throw new UnprocessableEntityException(e.getMessage()); + } + return identifierRest; + } + + private IdentifierRest registerDOI(Context context, Item item) + throws SQLException, AuthorizeException { + String identifier = null; + IdentifierRest identifierRest = new IdentifierRest(); + identifierRest.setIdentifierType("doi"); + try { + DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); + if (doiIdentifierProvider != null) { + String doiValue = doiIdentifierProvider.register(context, item, new TrueFilter()); + identifierRest.setValue(doiValue); + // Get new status + DOI doi = doiService.findByDoi(context, doiValue); + if (doi != null) { + identifierRest.setIdentifierStatus(DOIIdentifierProvider.statusText[doi.getStatus()]); + } + } else { + throw new IllegalStateException("No DOI provider is configured"); + } + } catch (IdentifierException e) { + throw new IllegalStateException("Failed to register identifier: " + identifier); + } + // We didn't exactly change the item, but we did queue an identifier which is closely associated with it, + // so we should update the last modified date here + itemService.updateLastModified(context, item); + context.complete(); + return identifierRest; + } + + @Override + public Class getDomainClass() { + return IdentifierRest.class; + } +} diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg index ba73880139be..8577ee6a40c0 100644 --- a/dspace/config/modules/identifiers.cfg +++ b/dspace/config/modules/identifiers.cfg @@ -41,7 +41,7 @@ # Show Register DOI button in item status page? # Default: false -#identifiers.item-status.registerDOI = true +#identifiers.item-status.register-doi = true # Which identifier types to show in submission step? # Default: handle, doi (currently the only supported identifier 'types') From 074233ce8d2f484b8dcf418607452431d5cad238 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 6 Feb 2023 16:18:28 +1300 Subject: [PATCH 285/394] [TLC-249] Refactor, include controller 'find DSO' operation --- .../app/rest/IdentifierRestController.java | 108 -------------- .../repository/IdentifierRestRepository.java | 139 ++++++++++++++++-- 2 files changed, 125 insertions(+), 122 deletions(-) delete mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java deleted file mode 100644 index 35b21ecc3dfb..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest; - -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; - -import java.io.IOException; -import java.net.URI; -import java.sql.SQLException; -import java.util.Arrays; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.atteo.evo.inflector.English; -import org.dspace.app.rest.converter.ConverterService; -import org.dspace.app.rest.model.DSpaceObjectRest; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.app.rest.utils.Utils; -import org.dspace.content.DSpaceObject; -import org.dspace.core.Context; -import org.dspace.identifier.IdentifierNotFoundException; -import org.dspace.identifier.IdentifierNotResolvableException; -import org.dspace.identifier.factory.IdentifierServiceFactory; -import org.dspace.identifier.service.IdentifierService; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.hateoas.Link; -import org.springframework.hateoas.TemplateVariable; -import org.springframework.hateoas.TemplateVariable.VariableType; -import org.springframework.hateoas.TemplateVariables; -import org.springframework.hateoas.UriTemplate; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/" + IdentifierRestController.CATEGORY) -public class IdentifierRestController implements InitializingBean { - public static final String CATEGORY = "pid_SDFSDFSDFSDF"; - - public static final String ACTION = "find"; - - public static final String PARAM = "id"; - - private static final Logger log = LogManager.getLogger(); - - @Autowired - private ConverterService converter; - - @Autowired - private Utils utils; - - @Autowired - private DiscoverableEndpointsService discoverableEndpointsService; - - @Override - public void afterPropertiesSet() throws Exception { - discoverableEndpointsService - .register(this, - Arrays.asList( - Link.of( - UriTemplate.of("/api/" + CATEGORY + "/" + ACTION, - new TemplateVariables( - new TemplateVariable(PARAM, VariableType.REQUEST_PARAM))), - CATEGORY))); - } - - @RequestMapping(method = RequestMethod.GET, value = ACTION, params = PARAM) - @SuppressWarnings("unchecked") - public void getDSObyIdentifier(HttpServletRequest request, - HttpServletResponse response, - @RequestParam(PARAM) String id) - throws IOException, SQLException { - - DSpaceObject dso = null; - Context context = ContextUtil.obtainContext(request); - IdentifierService identifierService = IdentifierServiceFactory - .getInstance().getIdentifierService(); - try { - dso = identifierService.resolve(context, id); - if (dso != null) { - DSpaceObjectRest dsor = converter.toRest(dso, utils.obtainProjection()); - URI link = linkTo(dsor.getController(), dsor.getCategory(), - English.plural(dsor.getType())) - .slash(dsor.getId()).toUri(); - response.setStatus(HttpServletResponse.SC_FOUND); - response.sendRedirect(link.toString()); - } else { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } - } catch (IdentifierNotFoundException e) { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - } catch (IdentifierNotResolvableException e) { - response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); - } finally { - context.abort(); - } - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java index 8f8c88a7e03d..8af5893ed971 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java @@ -7,25 +7,31 @@ */ package org.dspace.app.rest.repository; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +import java.io.IOException; +import java.net.URI; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Iterator; +import java.util.Arrays; import java.util.List; import java.util.UUID; -import java.util.function.Function; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.NotSupportedException; +import org.atteo.evo.inflector.English; import org.dspace.app.rest.DiscoverableEndpointsService; -import org.dspace.app.rest.IdentifierRestController; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.LinkNotFoundException; import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.IdentifierRest; import org.dspace.app.rest.repository.handler.service.UriListHandlerService; +import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -36,24 +42,39 @@ import org.dspace.identifier.DOI; import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.IdentifierNotFoundException; +import org.dspace.identifier.IdentifierNotResolvableException; +import org.dspace.identifier.factory.IdentifierServiceFactory; import org.dspace.identifier.service.DOIService; import org.dspace.identifier.service.IdentifierService; import org.dspace.services.factory.DSpaceServicesFactory; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.TemplateVariable; +import org.springframework.hateoas.TemplateVariables; +import org.springframework.hateoas.UriTemplate; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; /** - * Controller for exposition of vocabularies entry details for the submission + * Item REST Repository and Controller for persistent identifiers. + * The controller annotation and endpoint registration allows the "find DSO by identifier" method which was + * previously implmented in org.dspace.app.rest.IdentifierRestController * - * @author Andrea Bollini (andrea.bollini at 4science.it) + * @author Kim Shepherd */ -@Component("pid.identifier") -public class IdentifierRestRepository extends DSpaceRestRepository { +@RestController +@RequestMapping("/api/" + IdentifierRestRepository.CATEGORY) +@Component(IdentifierRestRepository.CATEGORY + "." + IdentifierRestRepository.NAME) +public class IdentifierRestRepository extends DSpaceRestRepository implements InitializingBean { @Autowired private DiscoverableEndpointsService discoverableEndpointsService; @Autowired @@ -67,6 +88,26 @@ public class IdentifierRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { @@ -75,7 +116,16 @@ public Page findAll(Context context, Pageable pageable) { return new PageImpl<>(results, pageable, 0); } + /** + * Find the identifier object for a given identifier string (eg. doi) + * @param context + * the dspace context + * @param identifier + * the rest object id + * @return + */ @PreAuthorize("permitAll()") + @RequestMapping(method = RequestMethod.GET) @Override public IdentifierRest findOne(Context context, String identifier) { DSpaceObject dso; @@ -105,14 +155,20 @@ public IdentifierRest findOne(Context context, String identifier) { identifierRest.setValue(handleService.getCanonicalForm(dso.getHandle())); } } else { - throw new LinkNotFoundException(IdentifierRestController.CATEGORY, IdentifierRest.NAME, identifier); + throw new LinkNotFoundException(IdentifierRestRepository.CATEGORY, IdentifierRest.NAME, identifier); } } catch (SQLException | IdentifierException e) { - throw new LinkNotFoundException(IdentifierRestController.CATEGORY, IdentifierRest.NAME, identifier); + throw new LinkNotFoundException(IdentifierRestRepository.CATEGORY, IdentifierRest.NAME, identifier); } return identifierRest; } + /** + * Find identifiers associated with a given item + * @param uuid + * @param pageable + * @return + */ @SearchRestMethod(name = "findByItem") @PreAuthorize("permitAll()") public Page findByItem(@Parameter(value = "uuid", required = true) @@ -132,19 +188,20 @@ public Page findByItem(@Parameter(value = "uuid", required = tru results.add(new IdentifierRest(handleUrl, "handle", null)); } } catch (SQLException | IdentifierException e) { - throw new LinkNotFoundException(IdentifierRestController.CATEGORY, IdentifierRest.NAME, uuid); + throw new LinkNotFoundException(IdentifierRestRepository.CATEGORY, IdentifierRest.NAME, uuid); } // Return list of identifiers for this DSpaceObject return new PageImpl<>(results, pageable, results.size()); } /** - * Right now, the only supported identifier type is DOI + * Create (mint / queue for registration) a new persistent identifier of a given type (eg DOI) for an item + * Currently, the only supported identifier type for this operation is "doi" + * * @param context * the dspace context * @param list - * The list of Strings that will be used as data for the object that's to be created - * This list is retrieved from the uri-list body + * A uri-list with the item URI for which to create an identifier * @return * @throws AuthorizeException * @throws SQLException @@ -187,6 +244,15 @@ protected IdentifierRest createAndReturn(Context context, List list) return identifierRest; } + /** + * Perform DOI registration, skipping any other filters used. + * + * @param context + * @param item + * @return + * @throws SQLException + * @throws AuthorizeException + */ private IdentifierRest registerDOI(Context context, Item item) throws SQLException, AuthorizeException { String identifier = null; @@ -216,6 +282,51 @@ private IdentifierRest registerDOI(Context context, Item item) return identifierRest; } + + /** + * Redirect to a DSO page, given an identifier + * + * @param request HTTP request + * @param response HTTP response + * @param id The persistent identifier (eg. handle, DOI) to search for + * @throws IOException + * @throws SQLException + */ + @RequestMapping(method = RequestMethod.GET, value = "find", params = "id") + @SuppressWarnings("unchecked") + public void getDSObyIdentifier(HttpServletRequest request, + HttpServletResponse response, + @RequestParam("id") String id) + throws IOException, SQLException { + + DSpaceObject dso; + Context context = ContextUtil.obtainContext(request); + IdentifierService identifierService = IdentifierServiceFactory + .getInstance().getIdentifierService(); + try { + // Resolve identifier to a DSpace object + dso = identifierService.resolve(context, id); + if (dso != null) { + // Convert and respond with a redirect to the object itself + DSpaceObjectRest dsor = converter.toRest(dso, utils.obtainProjection()); + URI link = linkTo(dsor.getController(), dsor.getCategory(), + English.plural(dsor.getType())) + .slash(dsor.getId()).toUri(); + response.setStatus(HttpServletResponse.SC_FOUND); + response.sendRedirect(link.toString()); + } else { + // No object could be found + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } catch (IdentifierNotFoundException e) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + } catch (IdentifierNotResolvableException e) { + response.setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); + } finally { + context.abort(); + } + } + @Override public Class getDomainClass() { return IdentifierRest.class; From c2f0159414f6606aae31177ac0c759632e69e30a Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 6 Feb 2023 16:52:34 +1300 Subject: [PATCH 286/394] [TLC-249] Throw not implemented for findOne and findAll --- .../repository/IdentifierRestRepository.java | 56 +++++-------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java index 8af5893ed971..1c08b96d0da2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java @@ -85,12 +85,10 @@ public class IdentifierRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List results = new ArrayList<>(); - //return converter.toRestPage(results, pageable, utils.obtainProjection()); - return new PageImpl<>(results, pageable, 0); + throw new RepositoryMethodNotImplementedException(IdentifierRest.NAME, "findAll"); } /** - * Find the identifier object for a given identifier string (eg. doi) + * Find the identifier object for a given identifier string (eg. doi). + * Not implemented -- Tomcat interprets %2F as path separators which means + * parameters are a safer way to handle these operations + * * @param context * the dspace context * @param identifier @@ -125,42 +132,9 @@ public Page findAll(Context context, Pageable pageable) { * @return */ @PreAuthorize("permitAll()") - @RequestMapping(method = RequestMethod.GET) @Override public IdentifierRest findOne(Context context, String identifier) { - DSpaceObject dso; - IdentifierRest identifierRest = new IdentifierRest(); - try { - // Resolve to an object first - if that fails then this is not a valid identifier anyway. - dso = identifierService.resolve(context, identifier); - if (dso != null) { - // DSpace has no concept of a higher-level Identifier object, so in order to detect the type - // and return sufficient information, we have to try the identifier types we know are currently - // supported. - // First, try to resolve to a handle. - dso = handleService.resolveToObject(context, identifier); - if (dso == null) { - // No object found for a handle, try DOI - DOI doi = doiService.findByDoi(context, identifier); - if (doi != null) { - String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); - identifierRest.setIdentifierType("doi"); - identifierRest.setIdentifierStatus(DOIIdentifierProvider.statusText[doi.getStatus()]); - identifierRest.setValue(doiUrl); - } - } else { - // Handle found - identifierRest.setIdentifierType("handle"); - identifierRest.setIdentifierStatus(null); - identifierRest.setValue(handleService.getCanonicalForm(dso.getHandle())); - } - } else { - throw new LinkNotFoundException(IdentifierRestRepository.CATEGORY, IdentifierRest.NAME, identifier); - } - } catch (SQLException | IdentifierException e) { - throw new LinkNotFoundException(IdentifierRestRepository.CATEGORY, IdentifierRest.NAME, identifier); - } - return identifierRest; + throw new RepositoryMethodNotImplementedException(IdentifierRest.NAME, "findOne"); } /** From b732764dac9edd0a67688c4596f36a545a221aa0 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 6 Feb 2023 17:57:40 +1300 Subject: [PATCH 287/394] [TLC-249] Fixes and improvements as per review feedback --- .../main/java/org/dspace/ctask/general/RegisterDOI.java | 4 +++- .../org/dspace/identifier/FilteredIdentifierProvider.java | 7 ++++--- .../java/org/dspace/identifier/IdentifierServiceImpl.java | 1 - .../app/rest/repository/ItemIdentifierLinkRepository.java | 5 ++--- .../org/dspace/app/rest/repository/ItemRestRepository.java | 3 --- .../dspace/app/rest/submit/step/ShowIdentifiersStep.java | 2 +- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java index 8cc5f85d0562..6ed8edb51d7f 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java @@ -42,6 +42,7 @@ public class RegisterDOI extends AbstractCurationTask { private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RegisterDOI.class); // DOI provider private DOIIdentifierProvider provider; + private Filter trueFilter; /** * Initialise the curation task and read configuration, instantiate the DOI provider @@ -55,6 +56,7 @@ public void init(Curator curator, String taskId) throws IOException { ", distributed = " + distributed); // Instantiate DOI provider singleton provider = new DSpace().getSingletonService(DOIIdentifierProvider.class); + trueFilter = new DSpace().getSingletonService(TrueFilter.class); } /** @@ -120,7 +122,7 @@ private String register(Item item) { // Attempt DOI registration and report successes and failures try { Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter.curation", - new TrueFilter()); + trueFilter); doi = provider.register(Curator.curationContext(), item, filter); if (doi != null) { String message = "New DOI minted in database for item " + item.getHandle() + ": " + doi diff --git a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java index 440ee3100fa7..beee47338e9a 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java @@ -9,11 +9,13 @@ package org.dspace.identifier; import java.sql.SQLException; +import java.util.Optional; import org.dspace.content.DSpaceObject; import org.dspace.content.logic.Filter; import org.dspace.content.logic.TrueFilter; import org.dspace.core.Context; +import org.dspace.utils.DSpace; /** * This abstract class adds extra method signatures so that implementing IdentifierProviders can @@ -24,16 +26,15 @@ */ public abstract class FilteredIdentifierProvider extends IdentifierProvider { - protected Filter filter = new TrueFilter(); + protected Filter filter = new DSpace().getSingletonService(TrueFilter.class); /** * Setter for spring to set the default filter from the property in configuration XML * @param filter - an object implementing the org.dspace.content.logic.Filter interface */ public void setFilter(Filter filter) { - this.filter = filter; if (this.filter == null) { - this.filter = new TrueFilter(); + this.filter = filter; } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java index 15e0e1c1ec13..b98aea24fa08 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/IdentifierServiceImpl.java @@ -46,7 +46,6 @@ public class IdentifierServiceImpl implements IdentifierService { protected HandleService handleService; protected IdentifierServiceImpl() { - } @Autowired(required = true) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java index 83e7b808ba99..b7f07674a750 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java @@ -66,9 +66,8 @@ public IdentifiersRest getIdentifiers(@Nullable HttpServletRequest request, try { if (doi != null) { String doiUrl = doiService.DOIToExternalForm(doi.getDoi()); - IdentifierRest identifierRest = new IdentifierRest( - doiUrl, "doi", DOIIdentifierProvider.statusText[doi.getStatus()]); - identifierRestList.add(identifierRest); + identifierRestList.add(new IdentifierRest( + doiUrl, "doi", DOIIdentifierProvider.statusText[doi.getStatus()])); } if (handle != null) { identifierRestList.add(new IdentifierRest(handle, "handle", null)); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index ce9679b8cf18..597f47f49553 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -93,9 +93,6 @@ public class ItemRestRepository extends DSpaceObjectRestRepository Date: Mon, 6 Feb 2023 18:17:28 +1300 Subject: [PATCH 288/394] [TLC-249] Checkstyle fix --- .../java/org/dspace/identifier/FilteredIdentifierProvider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java index beee47338e9a..0ec04d61b548 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java @@ -9,7 +9,6 @@ package org.dspace.identifier; import java.sql.SQLException; -import java.util.Optional; import org.dspace.content.DSpaceObject; import org.dspace.content.logic.Filter; From b13f36148d8f03411004b65fdaf6b590129986a1 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 7 Feb 2023 11:33:48 +1300 Subject: [PATCH 289/394] [TLC-249] return value as id for IdentifierRest --- .../main/java/org/dspace/app/rest/model/IdentifierRest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java index 8dc9f22d6893..59a0de5a31c6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -110,6 +110,11 @@ public String getCategory() { return "ppid"; } + @Override + public String getId() { + return getValue(); + } + @Override public Class getController() { return RestResourceController.class; From e63727e39e7010b15369bb98188d5379f0ba724d Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 12:27:49 +1300 Subject: [PATCH 290/394] [TLC-249] Fix category type in IdentifierRest --- .../src/main/java/org/dspace/app/rest/model/IdentifierRest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java index 59a0de5a31c6..6cfb147ea314 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/IdentifierRest.java @@ -107,7 +107,7 @@ public void setIdentifierStatus(String identifierStatus) { @Override public String getCategory() { - return "ppid"; + return "pid"; } @Override From 4997954f2313a720945dd941812c033d2930a239 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 12:28:28 +1300 Subject: [PATCH 291/394] [TLC-249] Update integration tests for IdentifierRestRepository --- .../app/rest/IdentifierRestControllerIT.java | 101 ------ .../app/rest/IdentifierRestRepositoryIT.java | 333 ++++++++++++++++++ .../app/rest/ItemIdentifierControllerIT.java | 178 ---------- 3 files changed, 333 insertions(+), 279 deletions(-) delete mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java create mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java delete mode 100644 dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java deleted file mode 100644 index 9927d3728622..000000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestControllerIT.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.builder.CollectionBuilder; -import org.dspace.builder.CommunityBuilder; -import org.dspace.builder.ItemBuilder; -import org.dspace.content.Collection; -import org.dspace.content.Item; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -/** - * Integration test for the identifier resolver - * - * @author Andrea Bollini (andrea.bollini at 4science.it) - */ -public class IdentifierRestControllerIT extends AbstractControllerIntegrationTest { - - @Before - public void setup() throws Exception { - super.setUp(); - } - - @Test - public void testValidIdentifier() throws Exception { - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - // We create a top community to receive an identifier - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - - context.restoreAuthSystemState(); - - String handle = parentCommunity.getHandle(); - String communityDetail = REST_SERVER_URL + "core/communities/" + parentCommunity.getID(); - - getClient().perform(get("/api/pid/find?id={handle}",handle)) - .andExpect(status().isFound()) - //We expect a Location header to redirect to the community details - .andExpect(header().string("Location", communityDetail)); - } - - @Test - - public void testValidIdentifierItemHandlePrefix() throws Exception { - //We turn off the authorization system in order to create the structure as defined below - context.turnOffAuthorisationSystem(); - - // Create an item with a handle identifier - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection owningCollection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Owning Collection") - .build(); - Item item = ItemBuilder.createItem(context, owningCollection) - .withTitle("Test item") - .build(); - - String handle = item.getHandle(); - String itemLocation = REST_SERVER_URL + "core/items/" + item.getID(); - - getClient().perform(get("/api/pid/find?id=hdl:{handle}", handle)) - .andExpect(status().isFound()) - // We expect a Location header to redirect to the item's page - .andExpect(header().string("Location", itemLocation)); - } - - @Test - public void testUnexistentIdentifier() throws Exception { - getClient().perform(get("/api/pid/find?id={id}","fakeIdentifier")) - .andExpect(status().isNotFound()); - } - - @Test - @Ignore - /** - * This test will check the return status code when no id is supplied. It currently fails as our - * RestResourceController take the precedence over the pid controller returning a 404 Repository not found - * - * @throws Exception - */ - public void testMissingIdentifierParameter() throws Exception { - getClient().perform(get("/api/pid/find")) - .andExpect(status().isUnprocessableEntity()); - } -} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java new file mode 100644 index 000000000000..ea807a9308a3 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java @@ -0,0 +1,333 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.junit.Assert.assertNotNull; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; +import org.dspace.identifier.DOI; +import org.dspace.identifier.DOIIdentifierProvider; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.DOIService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.http.MediaType; + +/** + * Test getting and registering item identifiers + * + * @author Kim Shepherd + */ +public class IdentifierRestRepositoryIT extends AbstractControllerIntegrationTest { + @Before + public void setup() throws Exception { + super.setUp(); + } + + @Test + public void testValidIdentifier() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + // We create a top community to receive an identifier + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + context.restoreAuthSystemState(); + + String handle = parentCommunity.getHandle(); + String communityDetail = REST_SERVER_URL + "core/communities/" + parentCommunity.getID(); + + getClient().perform(get("/api/pid/find?id={handle}",handle)) + .andExpect(status().isFound()) + //We expect a Location header to redirect to the community details + .andExpect(header().string("Location", communityDetail)); + } + + @Test + + public void testValidIdentifierItemHandlePrefix() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + // Create an item with a handle identifier + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection owningCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Owning Collection") + .build(); + Item item = ItemBuilder.createItem(context, owningCollection) + .withTitle("Test item") + .build(); + + String handle = item.getHandle(); + String itemLocation = REST_SERVER_URL + "core/items/" + item.getID(); + + getClient().perform(get("/api/pid/find?id=hdl:{handle}", handle)) + .andExpect(status().isFound()) + // We expect a Location header to redirect to the item's page + .andExpect(header().string("Location", itemLocation)); + } + + @Test + public void testUnexistentIdentifier() throws Exception { + getClient().perform(get("/api/pid/find?id={id}","fakeIdentifier")) + .andExpect(status().isNotFound()); + } + + @Test + @Ignore + /** + * This test will check the return status code when no id is supplied. It currently fails as our + * RestResourceController take the precedence over the pid controller returning a 404 Repository not found + * + * @throws Exception + */ + public void testMissingIdentifierParameter() throws Exception { + getClient().perform(get("/api/pid/find")) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + public void testRegisterDoiForItem() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + + //** GIVEN ** + //1. A community-collection structure with one parent community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + // This item should not have a DOI + DOIIdentifierProvider doiIdentifierProvider = + DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", + org.dspace.identifier.DOIIdentifierProvider.class); + doiIdentifierProvider.delete(context, publicItem1); + + // Body of POST to create an identifier for public item 1 + String uriList = "https://localhost:8080/server/api/core/items/" + publicItem1.getID(); + + // A non-admin should get an unauthorised error from REST method preauth + // Expect first forbidden + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(post("/api/pid/identifiers") + .queryParam("type", "doi") + .contentType(MediaType.parseMediaType("text/uri-list")) + .content(uriList)) + .andExpect(status().isForbidden()); + + // Set token to admin credentials + token = getAuthToken(admin.getEmail(), password); + + // Expect a successful 201 CREATED for this item with no DOI + getClient(token).perform(post("/api/pid/identifiers") + .queryParam("type", "doi") + .contentType(MediaType.parseMediaType("text/uri-list")) + .content(uriList)) + .andExpect(status().isCreated()); + + // Expected 400 BAD REQUEST status code for a DOI already in REGISTERED / TO_BE_REGISTERED state + getClient(token).perform(post("/api/pid/identifiers") + .queryParam("type", "doi") + .contentType(MediaType.parseMediaType("text/uri-list")) + .content(uriList)) + .andExpect(status().isBadRequest()); + + // Get the doi we minted and queued for registration + DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); + // The DOI should not be null + assertNotNull(doi); + // The DOI status should be TO_BE_REGISTERED + Assert.assertEquals(DOIIdentifierProvider.TO_BE_REGISTERED, doi.getStatus()); + + // Now, set the DOI status back to pending and update + doi.setStatus(DOIIdentifierProvider.PENDING); + doiService.update(context, doi); + + // Do another POST, again this should return 201 CREATED as we shift the DOI from PENDING to TO_BE_REGISTERED + getClient(token).perform(post("/api/pid/identifiers") + .queryParam("type", "doi") + .contentType(MediaType.parseMediaType("text/uri-list")) + .content(uriList)) + .andExpect(status().isCreated()); + + context.restoreAuthSystemState(); + } + + @Test + public void testGetIdentifiersForItemByLink() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + + //1. A community-collection structure with one parent community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + String doiString = "10.5072/dspace-identifier-test-" + publicItem1.getID(); + + // Use the DOI service to directly manipulate the DOI on this object so that we can predict and + // test values via the REST request + DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); + + // Assert non-null DOI, since we should be minting them automatically here + assertNotNull(doi); + + // Set specific string and state we expect to get back from a REST request + doi.setDoi(doiString); + doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); + doiService.update(context, doi); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + + // Get identifiers for this item - we expect a 200 OK response and the type of the resource is plural + // "identifiers" + getClient(token).perform(get("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()).andExpect(jsonPath("$.type").value("identifiers")); + + // Expect an array of identifiers + getClient(token).perform(get("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.identifiers").isArray()); + + // Expect a valid DOI with the value, type and status we expect + getClient(token).perform(get("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.identifiers[0].type").value("identifier")) + .andExpect(jsonPath("$.identifiers[0].value").value(doiService.DOIToExternalForm(doiString))) + .andExpect(jsonPath("$.identifiers[0].identifierType").value("doi")) + .andExpect(jsonPath("$.identifiers[0].identifierStatus") + .value(DOIIdentifierProvider.statusText[DOIIdentifierProvider.IS_REGISTERED])); + + // Expect a valid Handle with the value, type we expect + getClient(token).perform(get("/api/core/items/" + + publicItem1.getID().toString() + "/identifiers")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.identifiers[1].type").value("identifier")) + .andExpect(jsonPath("$.identifiers[1].value") + .value(handleService.getCanonicalForm(publicItem1.getHandle()))) + .andExpect(jsonPath("$.identifiers[1].identifierType").value("handle")); + + } + + @Test + public void testFindIdentifiersByItem() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); + HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + + //1. A community-collection structure with one parent community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. A public item that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + String doiString = "10.5072/dspace-identifier-test-" + publicItem1.getID(); + + // Use the DOI service to directly manipulate the DOI on this object so that we can predict and + // test values via the REST request + DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); + + // Assert non-null DOI, since we should be minting them automatically here + assertNotNull(doi); + + // Set specific string and state we expect to get back from a REST request + doi.setDoi(doiString); + doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); + doiService.update(context, doi); + + context.restoreAuthSystemState(); + + String token = getAuthToken(eperson.getEmail(), password); + + // Get identifiers for this item - we expect a 200 OK response and the type of the resource is plural + // "identifiers" + getClient(token).perform(get("/api/pid/identifiers/search/findByItem").queryParam("uuid", + publicItem1.getID().toString())) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.identifiers").exists()); + + // Expect an array of identifiers + getClient(token).perform(get("/api/pid/identifiers/search/findByItem").queryParam("uuid", + publicItem1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.identifiers").isArray()); + + // Expect a valid DOI with the value, type and status we expect + getClient(token).perform(get("/api/pid/identifiers/search/findByItem").queryParam("uuid", + publicItem1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.identifiers[0].type").value("identifier")) + .andExpect(jsonPath("$._embedded.identifiers[0].value").value(doiService.DOIToExternalForm(doiString))) + .andExpect(jsonPath("$._embedded.identifiers[0].identifierType").value("doi")) + .andExpect(jsonPath("$._embedded.identifiers[0].identifierStatus") + .value(DOIIdentifierProvider.statusText[DOIIdentifierProvider.IS_REGISTERED])); + + // Expect a valid Handle with the value, type we expect + getClient(token).perform(get("/api/pid/identifiers/search/findByItem").queryParam("uuid", + publicItem1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.identifiers[1].type").value("identifier")) + .andExpect(jsonPath("$._embedded.identifiers[1].value") + .value(handleService.getCanonicalForm(publicItem1.getHandle()))) + .andExpect(jsonPath("$._embedded.identifiers[1].identifierType").value("handle")); + + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java deleted file mode 100644 index 8f08ddcf5051..000000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemIdentifierControllerIT.java +++ /dev/null @@ -1,178 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest; - -import static org.junit.Assert.assertNotNull; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.builder.CollectionBuilder; -import org.dspace.builder.CommunityBuilder; -import org.dspace.builder.ItemBuilder; -import org.dspace.content.Collection; -import org.dspace.content.Item; -import org.dspace.identifier.DOI; -import org.dspace.identifier.DOIIdentifierProvider; -import org.dspace.identifier.factory.IdentifierServiceFactory; -import org.dspace.identifier.service.DOIService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -/** - * Test getting and registering item identifiers - * - * @author Kim Shepherd - */ -public class ItemIdentifierControllerIT extends AbstractControllerIntegrationTest { - @Before - public void setup() throws Exception { - super.setUp(); - } - - @Test - public void testRegisterDoiForItem() throws Exception { - //Turn off the authorization system, otherwise we can't make the objects - context.turnOffAuthorisationSystem(); - - DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); - - //** GIVEN ** - //1. A community-collection structure with one parent community and two collections. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - - //2. A public item that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Public item 1") - .withIssueDate("2017-10-17") - .withAuthor("Smith, Donald") - .build(); - - // This item should not have a DOI - DOIIdentifierProvider doiIdentifierProvider = - DSpaceServicesFactory.getInstance().getServiceManager() - .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", - org.dspace.identifier.DOIIdentifierProvider.class); - doiIdentifierProvider.delete(context, publicItem1); - - // A non-admin should get an unauthorised error from REST method preauth - // Expect first forbidden - String token = getAuthToken(eperson.getEmail(), password); - getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers?type=doi").content("doi")) - .andExpect(status().isForbidden()); - - // Set token to admin credentials - token = getAuthToken(admin.getEmail(), password); - - // Expect a successful 201 CREATED for this item with no DOI - getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers?type=doi").content("hello")) - .andExpect(status().isCreated()); - - // Expected 302 FOUND status code for a DOI already in REGISTERED / TO_BE_REGISTERED state - getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers?type=doi")) - .andExpect(status().isFound()); - - // Get the doi we minted and queued for registration - DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); - // The DOI should not be null - assertNotNull(doi); - // The DOI status should be TO_BE_REGISTERED - Assert.assertEquals(DOIIdentifierProvider.TO_BE_REGISTERED, doi.getStatus()); - - // Now, set the DOI status back to pending and update - doi.setStatus(DOIIdentifierProvider.PENDING); - doiService.update(context, doi); - - // Do another POST, again this should return 201 CREATED as we shift the DOI from PENDING to TO_BE_REGISTERED - getClient(token).perform(post("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers?type=doi")) - .andExpect(status().isCreated()); - - context.restoreAuthSystemState(); - } - - @Test - public void testGetIdentifiersForItem() throws Exception { - //Turn off the authorization system, otherwise we can't make the objects - context.turnOffAuthorisationSystem(); - - DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); - - //1. A community-collection structure with one parent community and two collections. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - - //2. A public item that is readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Public item 1") - .withIssueDate("2017-10-17") - .withAuthor("Smith, Donald") - .build(); - - String doiString = "10.5072/dspace-identifier-test-" + publicItem1.getID(); - - // Use the DOI service to directly manipulate the DOI on this object so that we can predict and - // test values via the REST request - DOI doi = doiService.findDOIByDSpaceObject(context, publicItem1); - - // Assert non-null DOI, since we should be minting them automatically here - assertNotNull(doi); - - // Set specific string and state we expect to get back from a REST request - doi.setDoi(doiString); - doi.setStatus(DOIIdentifierProvider.IS_REGISTERED); - doiService.update(context, doi); - - context.restoreAuthSystemState(); - - String token = getAuthToken(eperson.getEmail(), password); - - // Get identifiers for this item - we expect a 200 OK response and the type of the resource is plural - // "identifiers" - getClient(token).perform(get("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) - .andExpect(status().isOk()).andExpect(jsonPath("$.type").value("identifiers")); - - // Expect an array of identifiers - getClient(token).perform(get("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.identifiers").isArray()); - - // Expect a valid DOI with the value, type and status we expect - getClient(token).perform(get("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.identifiers[0].type").value("identifier")) - .andExpect(jsonPath("$.identifiers[0].value").value(doiService.DOIToExternalForm(doiString))) - .andExpect(jsonPath("$.identifiers[0].identifierType").value("doi")) - .andExpect(jsonPath("$.identifiers[0].identifierStatus") - .value(DOIIdentifierProvider.statusText[DOIIdentifierProvider.IS_REGISTERED])); - - // Expect a valid Handle with the value, type we expect - getClient(token).perform(get("/api/core/items/" + - publicItem1.getID().toString() + "/identifiers")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.identifiers[1].type").value("identifier")) - .andExpect(jsonPath("$.identifiers[1].value").value(publicItem1.getHandle())) - .andExpect(jsonPath("$.identifiers[1].identifierType").value("handle")); - - } -} From dd0a7d7a63a04b18861a22784cc190da10e6687a Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 12:39:57 +1300 Subject: [PATCH 292/394] [TLC-249] Refactor TrueFilter to get instantiated as singleton --- .../org/dspace/content/logic/FilterUtils.java | 3 ++- .../org/dspace/ctask/general/RegisterDOI.java | 4 +++- .../identifier/DOIIdentifierProvider.java | 17 +++++++++++++---- .../identifier/FilteredIdentifierProvider.java | 7 ++++--- .../org/dspace/identifier/doi/DOIOrganiser.java | 3 ++- .../identifier/DOIIdentifierProviderTest.java | 4 +++- .../repository/IdentifierRestRepository.java | 9 +++++---- 7 files changed, 32 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java index 006d45de28a5..67a9acd17422 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java @@ -68,7 +68,8 @@ public static Map, Filter> getIdentifierFilters(Stri filters.put(DOI.class, filter); // This won't have an affect until handle providers implement filtering, but is an example of // how the filters can be used for other types - filters.put(Handle.class, new TrueFilter()); + filters.put(Handle.class, DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class)); return filters; } } diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java index 6ed8edb51d7f..0765d7b000d1 100644 --- a/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java +++ b/dspace-api/src/main/java/org/dspace/ctask/general/RegisterDOI.java @@ -21,6 +21,7 @@ import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; /** @@ -56,7 +57,8 @@ public void init(Curator curator, String taskId) throws IOException { ", distributed = " + distributed); // Instantiate DOI provider singleton provider = new DSpace().getSingletonService(DOIIdentifierProvider.class); - trueFilter = new DSpace().getSingletonService(TrueFilter.class); + trueFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class); } /** diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java index 533da2e34b69..9d20b4944852 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -29,6 +29,8 @@ import org.dspace.identifier.doi.DOIIdentifierException; import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; import org.dspace.identifier.service.DOIService; +import org.dspace.servicemanager.DSpaceServiceManager; +import org.dspace.services.factory.DSpaceServicesFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -493,7 +495,8 @@ public void updateMetadata(Context context, DSpaceObject dso, String identifier) // We can skip the filter here since we know the DOI already exists for the item log.debug("updateMetadata: found DOIByDSpaceObject: " + doiService.findDOIByDSpaceObject(context, dso).getDoi()); - updateFilter = new TrueFilter(); + updateFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class); } DOI doiRow = loadOrCreateDOI(context, dso, doi, updateFilter); @@ -1118,9 +1121,15 @@ protected void removeDOIFromObject(Context context, DSpaceObject dso, String doi public void checkMintable(Context context, Filter filter, DSpaceObject dso) throws DOIIdentifierNotApplicableException { if (filter == null) { - // If a null filter was passed and we have a good default filter to apply, apply it. - // Otherwise set to TrueFilter which means "no filtering" - filter = Objects.requireNonNullElseGet(this.filter, TrueFilter::new); + Filter trueFilter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class); + // If a null filter was passed, and we have a good default filter to apply, apply it. + // Otherwise, set to TrueFilter which means "no filtering" + if (this.filter != null) { + filter = this.filter; + } else { + filter = trueFilter; + } } // If the check fails, an exception will be thrown to be caught by the calling method if (contentServiceFactory.getDSpaceObjectService(dso).getTypeText(dso).equals("ITEM")) { diff --git a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java index 0ec04d61b548..c2254fa9a6fd 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/FilteredIdentifierProvider.java @@ -14,7 +14,7 @@ import org.dspace.content.logic.Filter; import org.dspace.content.logic.TrueFilter; import org.dspace.core.Context; -import org.dspace.utils.DSpace; +import org.dspace.services.factory.DSpaceServicesFactory; /** * This abstract class adds extra method signatures so that implementing IdentifierProviders can @@ -25,14 +25,15 @@ */ public abstract class FilteredIdentifierProvider extends IdentifierProvider { - protected Filter filter = new DSpace().getSingletonService(TrueFilter.class); + protected Filter filter = DSpaceServicesFactory.getInstance() + .getServiceManager().getServiceByName("always_true_filter", TrueFilter.class); /** * Setter for spring to set the default filter from the property in configuration XML * @param filter - an object implementing the org.dspace.content.logic.Filter interface */ public void setFilter(Filter filter) { - if (this.filter == null) { + if (filter != null) { this.filter = filter; } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java index 8af7f49e300b..088e2b1cbc87 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java @@ -80,7 +80,8 @@ public DOIOrganiser(Context context, DOIIdentifierProvider provider) { this.itemService = ContentServiceFactory.getInstance().getItemService(); this.doiService = IdentifierServiceFactory.getInstance().getDOIService(); this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - this.filter = new TrueFilter(); + this.filter = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class); } /** diff --git a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java index c891589cff07..acd23f473a64 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java @@ -658,7 +658,9 @@ public void testCreate_and_Register_DOI() Item item = newItem(); // Register, skipping the filter - String doi = provider.register(context, item, new TrueFilter()); + String doi = provider.register(context, item, + DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class)); // we want the created DOI to be returned in the following format: // doi:10./. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java index 1c08b96d0da2..08a6360e0c39 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java @@ -24,6 +24,7 @@ import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.LinkNotFoundException; import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; @@ -176,7 +177,7 @@ public Page findByItem(@Parameter(value = "uuid", required = tru * the dspace context * @param list * A uri-list with the item URI for which to create an identifier - * @return + * @return 201 Created with object JSON on success * @throws AuthorizeException * @throws SQLException * @throws RepositoryMethodNotImplementedException @@ -212,8 +213,6 @@ protected IdentifierRest createAndReturn(Context context, List list) } } catch (AuthorizeException e) { throw new RESTAuthorizationException(e); - } catch (IdentifierException e) { - throw new UnprocessableEntityException(e.getMessage()); } return identifierRest; } @@ -236,7 +235,9 @@ private IdentifierRest registerDOI(Context context, Item item) DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); if (doiIdentifierProvider != null) { - String doiValue = doiIdentifierProvider.register(context, item, new TrueFilter()); + String doiValue = doiIdentifierProvider.register(context, item, + DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( + "always_true_filter", TrueFilter.class)); identifierRest.setValue(doiValue); // Get new status DOI doi = doiService.findByDoi(context, doiValue); From cb3c9b1f2095bb15f943972e63106a00dda83c1b Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 14:41:39 +1300 Subject: [PATCH 293/394] [TLC-249] Construct canonical handle URL in identifier item link --- .../app/rest/repository/IdentifierRestRepository.java | 9 +++------ .../rest/repository/ItemIdentifierLinkRepository.java | 5 ++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java index 08a6360e0c39..1be569d18e5d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/IdentifierRestRepository.java @@ -207,9 +207,8 @@ protected IdentifierRest createAndReturn(Context context, List list) // Register things identifierRest = registerDOI(context, item); } else { - // Nothing to do here, return existing DOI - identifierRest = new IdentifierRest(doiService.DOIToExternalForm(doi.getDoi()), - "doi", DOIIdentifierProvider.statusText[doi.getStatus()]); + // Return bad request exception, as per other createAndReturn implementations (eg EPerson) + throw new DSpaceBadRequestException("The DOI is already registered or queued to be registered"); } } catch (AuthorizeException e) { throw new RESTAuthorizationException(e); @@ -235,9 +234,7 @@ private IdentifierRest registerDOI(Context context, Item item) DOIIdentifierProvider doiIdentifierProvider = DSpaceServicesFactory.getInstance().getServiceManager() .getServiceByName("org.dspace.identifier.DOIIdentifierProvider", DOIIdentifierProvider.class); if (doiIdentifierProvider != null) { - String doiValue = doiIdentifierProvider.register(context, item, - DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( - "always_true_filter", TrueFilter.class)); + String doiValue = doiIdentifierProvider.register(context, item, new TrueFilter()); identifierRest.setValue(doiValue); // Get new status DOI doi = doiService.findByDoi(context, doiValue); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java index b7f07674a750..0714b7329bff 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemIdentifierLinkRepository.java @@ -24,6 +24,7 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.handle.service.HandleService; import org.dspace.identifier.DOI; import org.dspace.identifier.DOIIdentifierProvider; import org.dspace.identifier.IdentifierException; @@ -48,6 +49,8 @@ public class ItemIdentifierLinkRepository extends AbstractDSpaceRestRepository i @Autowired DOIService doiService; + @Autowired + HandleService handleService; @PreAuthorize("hasPermission(#itemId, 'ITEM', 'READ')") public IdentifiersRest getIdentifiers(@Nullable HttpServletRequest request, @@ -70,7 +73,7 @@ public IdentifiersRest getIdentifiers(@Nullable HttpServletRequest request, doiUrl, "doi", DOIIdentifierProvider.statusText[doi.getStatus()])); } if (handle != null) { - identifierRestList.add(new IdentifierRest(handle, "handle", null)); + identifierRestList.add(new IdentifierRest(handleService.getCanonicalForm(handle), "handle", null)); } } catch (IdentifierException e) { throw new IllegalStateException("Failed to register identifier: " + e.getMessage()); From 94cb0c78ec5c87adae297eaf2690150091af9ea5 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 14:43:34 +1300 Subject: [PATCH 294/394] [TL-249] Remove example-doi_filter from config, add singleton attr to TrueFilter --- dspace/config/spring/api/item-filters.xml | 25 +---------------------- 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/dspace/config/spring/api/item-filters.xml b/dspace/config/spring/api/item-filters.xml index ae732b2ca048..24c463fb531e 100644 --- a/dspace/config/spring/api/item-filters.xml +++ b/dspace/config/spring/api/item-filters.xml @@ -20,7 +20,7 @@ - + - - - - - - - - - - - - - - - - From d0c202010df8a4bb82709a47e94445fd992a2884 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 14:44:21 +1300 Subject: [PATCH 295/394] [TL-249] Add always_true_filter to test REST identifier-service.xml config --- .../data/dspaceFolder/config/spring/api/identifier-service.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml index 206b801d0842..447d0a59dd1a 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml @@ -33,6 +33,7 @@ ref="org.dspace.services.ConfigurationService"/> + Date: Wed, 8 Feb 2023 14:46:20 +1300 Subject: [PATCH 296/394] [TL-249] Add identifiers.cfg and item-filters.xml config to test data --- .../config/modules/identifiers.cfg | 49 +++ .../config/spring/api/item-filters.xml | 370 ++++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 dspace-api/src/test/data/dspaceFolder/config/modules/identifiers.cfg create mode 100644 dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml diff --git a/dspace-api/src/test/data/dspaceFolder/config/modules/identifiers.cfg b/dspace-api/src/test/data/dspaceFolder/config/modules/identifiers.cfg new file mode 100644 index 000000000000..64512572ff73 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/modules/identifiers.cfg @@ -0,0 +1,49 @@ +#----------------------------------------------------------------------# +#---------------------IDENTIFIER CONFIGURATIONS------------------------# +#----------------------------------------------------------------------# +# These configs are used for additional identifier configuration such # +# as the Show Identifiers step which can "pre-mint" DOIs and Handles # +#----------------------------------------------------------------------# + +# Should configured identifiers (eg handle and DOI) be minted for (future) registration at workspace item creation? +# A handle created at this stage will act just like a regular handle created at archive time. +# A DOI created at this stage will be in a 'PENDING' status while in workspace and workflow. +# At the time of item install, the DOI filter (if any) will be applied and if the item matches the filter, the DOI +# status will be updated to TO_BE_REGISTERED. An administrator can also manually progress the DOI status, overriding +# any filters, in the item status page. +# This option doesn't require the Show Identifiers submission step to be visible. +# Default: false +identifiers.submission.register = false + +# This configuration property can be set to a filter name to determine if a PENDING DOI for an item +# should be queued for registration. If the filter doesn't match, the DOI will stay in PENDING or MINTED status +# so that the identifier itself persists in case it is considered for registration in the future. +# See doi-filter and other example filters in item-filters.xml. +# Default (always_true_filter) +identifiers.submission.filter.install = doi_filter + +# This optional configuration property can be set to a filter name, in case there are some initial rules to apply +# when first deciding whether a DOI should be be created for a new workspace item with a PENDING status. +# This filter is only applied if identifiers.submission.register is true. +# This filter is updated as submission data is saved. +# Default: (always_true_filter) +identifiers.submission.filter.workspace = doi_filter + +# If true, the workspace filter will be applied as submission data is saved. If the filter no longer +# matches the item, the DOI will be shifted into a MINTED status and not displayed in the submission section. +# If false, then once a DOI has been created with PENDING status it will remain that way until final item install +# Default: true +#identifiers.submission.strip_pending_during_submission = true + +# This configuration property can be set to a filter name to determine if an item processed by RegisterDOI curation +# task should be eligible for a DOI +identifiers.submission.filter.curation = always_true_filter + +# Show Register DOI button in item status page? +# Default: false +identifiers.item-status.register-doi = true + +# Which identifier types to show in submission step? +# Default: handle, doi (currently the only supported identifier 'types') +identifiers.submission.display = handle +identifiers.submission.display = doi \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml new file mode 100644 index 000000000000..836d4f089677 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/item-filters.xml @@ -0,0 +1,370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + article$ + bachelorThesis$ + masterThesis$ + doctoralThesis$ + book$ + bookPart$ + review$ + conferenceObject$ + lecture$ + workingPaper$ + preprint$ + report$ + annotation$ + contributionToPeriodical$ + patent$ + dataset$ + other$ + + + + + + + + + + + + + 123456789/20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 123456789/3 + 123456789/4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7efb70a2f88de3927706f754497dc40e63b20c3c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 14:53:09 +1300 Subject: [PATCH 297/394] [TL-249] Fix imports --- .../main/java/org/dspace/identifier/DOIIdentifierProvider.java | 1 - .../app/rest/authorization/impl/CanRegisterDOIFeature.java | 1 - .../org/dspace/app/rest/repository/ItemRestRepository.java | 1 - .../java/org/dspace/app/rest/IdentifierRestRepositoryIT.java | 3 ++- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java index 9d20b4944852..027dfddf6cc1 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -29,7 +29,6 @@ import org.dspace.identifier.doi.DOIIdentifierException; import org.dspace.identifier.doi.DOIIdentifierNotApplicableException; import org.dspace.identifier.service.DOIService; -import org.dspace.servicemanager.DSpaceServiceManager; import org.dspace.services.factory.DSpaceServicesFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java index df2b4d8f5c74..7b1493d56d89 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanRegisterDOIFeature.java @@ -6,7 +6,6 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest.authorization.impl; -import javax.ws.rs.NotAllowedException; import java.sql.SQLException; import org.dspace.app.rest.authorization.AuthorizationFeature; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 597f47f49553..16ce8629d17d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -45,7 +45,6 @@ import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; -import org.dspace.identifier.service.IdentifierService; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java index ea807a9308a3..27e21e47760d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/IdentifierRestRepositoryIT.java @@ -10,8 +10,9 @@ import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.builder.CollectionBuilder; From 2b957e2bb9c074c1a60ab25243e45ef4017ac08b Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 15:28:45 +1300 Subject: [PATCH 298/394] [TL-249] Revert accidental commit to RestResourceController --- .../main/java/org/dspace/app/rest/RestResourceController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index 167b111f82c1..b82b4830753c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -125,7 +125,6 @@ public void afterPropertiesSet() { // this doesn't work as we don't have an active http request // see https://github.com/spring-projects/spring-hateoas/issues/408 // Link l = linkTo(this.getClass(), r).withRel(r); - log.error(r); String[] split = r.split("\\.", 2); String plural = English.plural(split[1]); Link l = Link.of("/api/" + split[0] + "/" + plural, plural); From 555363e9b7af87995e503b6a0e30568682183417 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 16:44:44 +1300 Subject: [PATCH 299/394] [TL-249] Javadoc and other small fixes, DOI unit tests --- .../java/org/dspace/content/logic/Filter.java | 5 ++ .../org/dspace/content/logic/FilterUtils.java | 17 ++++-- .../identifier/DOIIdentifierProvider.java | 4 +- .../identifier/DOIIdentifierProviderTest.java | 55 +++++++++++++++++++ 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/logic/Filter.java b/dspace-api/src/main/java/org/dspace/content/logic/Filter.java index 97ab73ddfa88..f789860e7743 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/Filter.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/Filter.java @@ -34,5 +34,10 @@ public interface Filter extends LogicalStatement, BeanNameAware { @Override boolean getResult(Context context, Item item) throws LogicalStatementException; + /** + * Get the name of a filter. This can be used by filters which make use of BeanNameAware + * to return the bean name. + * @return the id/name of this spring bean + */ String getName(); } diff --git a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java index 67a9acd17422..a7a29e86e04e 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java @@ -28,9 +28,11 @@ public class FilterUtils { ConfigurationService configurationService; /** - * Get a Filter by name + * Get a Filter by configuration property name + * For example, if a module has implemented "my-feature.filter" configuration property + * this method will return a filter with the ID specified by the configuration property * @param property DSpace configuration property name (Apache Commons config) - * @return Filter or null + * @return Filter object, with a bean ID configured for this property key, or null */ public static Filter getFilterFromConfiguration(String property) { String filterName = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty(property); @@ -41,9 +43,11 @@ public static Filter getFilterFromConfiguration(String property) { } /** - * Get a Filter by name + * Get a Filter by configuration property name + * For example, if a module has implemented "my-feature.filter" configuration property + * this method will return a filter with the ID specified by the configuration property * @param property DSpace configuration property name (Apache Commons config) - * @return Filter or null + * @return Filter object, with a bean ID configured for this property key, or default filter */ public static Filter getFilterFromConfiguration(String property, Filter defaultFilter) { Filter filter = getFilterFromConfiguration(property); @@ -55,6 +59,11 @@ public static Filter getFilterFromConfiguration(String property, Filter defaultF /** * Get a map of identifier types and filters to use when creating workspace or archived items + * This is used by services installing new archived or workspace items to filter by identifier type + * as some filters should apply to DOI creation but not Handle creation, and so on. + * + * Status + * @param status is a suffix used when finding the appropriate filter from configuration * @return */ public static Map, Filter> getIdentifierFilters(String status) { diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java index 027dfddf6cc1..b70eda960d35 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIIdentifierProvider.java @@ -500,8 +500,8 @@ public void updateMetadata(Context context, DSpaceObject dso, String identifier) DOI doiRow = loadOrCreateDOI(context, dso, doi, updateFilter); - if (PENDING.equals(doiRow.getStatus())) { - log.info("Not updating metadata for PENDING doi: " + doi); + if (PENDING.equals(doiRow.getStatus()) || MINTED.equals(doiRow.getStatus())) { + log.info("Not updating metadata for PENDING or MINTED doi: " + doi); return; } diff --git a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java index acd23f473a64..b71e93b8e061 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java @@ -758,6 +758,61 @@ public void testDelete_all_DOIs() DOIIdentifierProvider.TO_BE_DELETED.equals(doiRow2.getStatus())); } + @Test + public void testUpdateMetadataSkippedForPending() throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + WorkflowException { + context.turnOffAuthorisationSystem(); + Item item = newItem(); + // Mint a new DOI with PENDING status + String doi1 = this.createDOI(item, DOIIdentifierProvider.PENDING, true); + // Update metadata for the item. + // This would normally shift status to UPDATE_REGISTERED, UPDATE_BEFORE_REGISTERING or UPDATE_RESERVED. + // But if the DOI is just pending, it should return without changing anything. + provider.updateMetadata(context, item, doi1); + // Get the DOI from the service + DOI doi = doiService.findDOIByDSpaceObject(context, item); + // Ensure it is still PENDING + assertEquals("Status of updated DOI did not remain PENDING", + DOIIdentifierProvider.PENDING, doi.getStatus()); + context.restoreAuthSystemState(); + } + + @Test + public void testUpdateMetadataSkippedForMinted() throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + WorkflowException { + context.turnOffAuthorisationSystem(); + Item item = newItem(); + // Mint a new DOI with MINTED status + String doi1 = this.createDOI(item, DOIIdentifierProvider.MINTED, true); + // Update metadata for the item. + // This would normally shift status to UPDATE_REGISTERED, UPDATE_BEFORE_REGISTERING or UPDATE_RESERVED. + // But if the DOI is just minted, it should return without changing anything. + provider.updateMetadata(context, item, doi1); + // Get the DOI from the service + DOI doi = doiService.findDOIByDSpaceObject(context, item); + // Ensure it is still MINTED + assertEquals("Status of updated DOI did not remain PENDING", + DOIIdentifierProvider.MINTED, doi.getStatus()); + context.restoreAuthSystemState(); + } + + @Test + public void testLoadOrCreateDOIReturnsMintedStatus() throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + WorkflowException { + Item item = newItem(); + // Mint a DOI without an explicit reserve or register context + String mintedDoi = provider.mint(context, item, DSpaceServicesFactory.getInstance() + .getServiceManager().getServiceByName("always_true_filter", TrueFilter.class)); + DOI doi = doiService.findByDoi(context, mintedDoi.substring(DOI.SCHEME.length())); + // This should be minted + assertEquals("DOI is not of 'minted' status", DOIIdentifierProvider.MINTED, doi.getStatus()); + provider.updateMetadata(context, item, mintedDoi); + DOI secondFind = doiService.findByDoi(context, mintedDoi.substring(DOI.SCHEME.length())); + // After an update, this should still be minted + assertEquals("DOI is not of 'minted' status", + DOIIdentifierProvider.MINTED, secondFind.getStatus()); + + } // test the following methods using the MockDOIConnector. // updateMetadataOnline From c4af5f2bffb18fa5f6580767f3ce958f8580d0e7 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 17:03:13 +1300 Subject: [PATCH 300/394] [TL-249] Javadoc and other small fixes Including stripping $Revision$ from filter javadoc. --- .../dspace/content/InstallItemServiceImpl.java | 4 ++-- .../dspace/content/WorkspaceItemServiceImpl.java | 4 ++-- .../org/dspace/content/logic/DefaultFilter.java | 1 - .../org/dspace/content/logic/FilterUtils.java | 15 ++++++++------- .../dspace/content/logic/LogicalStatement.java | 1 - .../content/logic/LogicalStatementException.java | 1 - .../org/dspace/content/logic/TestLogicRunner.java | 1 - .../logic/condition/AbstractCondition.java | 1 - .../logic/condition/BitstreamCountCondition.java | 1 - .../dspace/content/logic/condition/Condition.java | 1 - .../logic/condition/InCollectionCondition.java | 1 - .../logic/condition/InCommunityCondition.java | 1 - .../logic/condition/IsArchivedCondition.java | 1 - .../logic/condition/IsWithdrawnCondition.java | 1 - .../condition/MetadataValueMatchCondition.java | 1 - .../condition/MetadataValuesMatchCondition.java | 1 - .../logic/condition/ReadableByGroupCondition.java | 1 - .../content/logic/operator/AbstractOperator.java | 1 - .../org/dspace/content/logic/operator/And.java | 1 - .../org/dspace/content/logic/operator/Nand.java | 1 - .../org/dspace/content/logic/operator/Not.java | 1 - .../org/dspace/content/logic/operator/Or.java | 1 - .../identifier/service/IdentifierService.java | 12 ++++++++++++ 23 files changed, 24 insertions(+), 30 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index 688d924f59eb..403d2f6fb67e 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -67,8 +67,8 @@ public Item installItem(Context c, InProgressSubmission is, AuthorizeException { Item item = is.getItem(); Collection collection = is.getCollection(); - // Get map of filters to use for identifier types - Map, Filter> filters = FilterUtils.getIdentifierFilters("install"); + // Get map of filters to use for identifier types. + Map, Filter> filters = FilterUtils.getIdentifierFilters(false); try { if (suppliedHandle == null) { // Register with the filters we've set up diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index 6bcf65106d5f..b847fd6352a8 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -177,8 +177,8 @@ public WorkspaceItem create(Context context, Collection collection, UUID uuid, b if (DSpaceServicesFactory.getInstance().getConfigurationService() .getBooleanProperty("identifiers.submission.register", false)) { try { - // Get map of filters to use for identifier types - Map, Filter> filters = FilterUtils.getIdentifierFilters("workspace"); + // Get map of filters to use for identifier types, while the item is in progress + Map, Filter> filters = FilterUtils.getIdentifierFilters(true); IdentifierServiceFactory.getInstance().getIdentifierService().register(context, item, filters); // Look for a DOI and move it to PENDING DOI doi = doiService.findDOIByDSpaceObject(context, item); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java b/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java index 936d12987ff6..1ac3930952af 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/DefaultFilter.java @@ -18,7 +18,6 @@ * statement as a property (unlike an operator) and takes no parameters (unlike a condition) * * @author Kim Shepherd - * @version $Revision$ */ public class DefaultFilter implements Filter { private LogicalStatement statement; diff --git a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java index a7a29e86e04e..a878d69e6ed8 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/FilterUtils.java @@ -61,18 +61,19 @@ public static Filter getFilterFromConfiguration(String property, Filter defaultF * Get a map of identifier types and filters to use when creating workspace or archived items * This is used by services installing new archived or workspace items to filter by identifier type * as some filters should apply to DOI creation but not Handle creation, and so on. - * - * Status - * @param status is a suffix used when finding the appropriate filter from configuration + * The in progress or archived status will be used to load the appropriate filter from configuration + *

+ * @param inProgress * @return */ - public static Map, Filter> getIdentifierFilters(String status) { - if (status == null) { - status = "install"; + public static Map, Filter> getIdentifierFilters(boolean inProgress) { + String configurationSuffix = "install"; + if (inProgress) { + configurationSuffix = "workspace"; } Map, Filter> filters = new HashMap<>(); // Put DOI 'can we create DOI on install / workspace?' filter - Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter." + status); + Filter filter = FilterUtils.getFilterFromConfiguration("identifiers.submission.filter." + configurationSuffix); // A null filter should be handled safely by the identifier provier (default, or "always true") filters.put(DOI.class, filter); // This won't have an affect until handle providers implement filtering, but is an example of diff --git a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java index 5fc3e76cd57f..0119f48b51f9 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatement.java @@ -17,7 +17,6 @@ * used as sub-statements in other Filters and Operators. * * @author Kim Shepherd - * @version $Revision$ */ public interface LogicalStatement { /** diff --git a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java index 758a0a71243f..4e3b3e3b7d78 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/LogicalStatementException.java @@ -12,7 +12,6 @@ * defined as spring beans. * * @author Kim Shepherd - * @version $Revision$ */ public class LogicalStatementException extends RuntimeException { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java b/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java index b78de7f1902b..bf218eaa8a0f 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/TestLogicRunner.java @@ -33,7 +33,6 @@ * A command-line runner used for testing a logical filter against an item, or all items * * @author Kim Shepherd - * @version $Revision$ */ public class TestLogicRunner { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java index 0202243265ed..ce5b274a8df0 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/AbstractCondition.java @@ -23,7 +23,6 @@ * Abstract class for conditions, to implement the basic getter and setter parameters * * @author Kim Shepherd - * @version $Revision$ */ public abstract class AbstractCondition implements Condition { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java index 635f0997d37e..36e506122e6f 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/BitstreamCountCondition.java @@ -18,7 +18,6 @@ * A condition to evaluate an item based on how many bitstreams it has in a particular bundle * * @author Kim Shepherd - * @version $Revision$ */ public class BitstreamCountCondition extends AbstractCondition { /** diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java index c86509899f65..7647dce4a4a4 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/Condition.java @@ -22,7 +22,6 @@ * operator is not a condition but also a logical statement. * * @author Kim Shepherd - * @version $Revision$ */ public interface Condition extends LogicalStatement { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java index 0aaa1bff1dea..df94f183d190 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCollectionCondition.java @@ -23,7 +23,6 @@ * if the item belongs to any of them. * * @author Kim Shepherd - * @version $Revision$ */ public class InCollectionCondition extends AbstractCondition { private static Logger log = LogManager.getLogger(InCollectionCondition.class); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java index 9f588f9c3be1..6a72011e7336 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/InCommunityCondition.java @@ -24,7 +24,6 @@ * if the item belongs to any of them. * * @author Kim Shepherd - * @version $Revision$ */ public class InCommunityCondition extends AbstractCondition { private final static Logger log = LogManager.getLogger(); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java index 95e4b05523f2..4f50d2b6f69f 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsArchivedCondition.java @@ -17,7 +17,6 @@ * A condition that returns true if the item is archived * * @author Kim Shepherd - * @version $Revision$ */ public class IsArchivedCondition extends AbstractCondition { private final static Logger log = LogManager.getLogger(); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java index 6424e6f35f3b..850b69bda0d8 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/IsWithdrawnCondition.java @@ -17,7 +17,6 @@ * A condition that returns true if the item is withdrawn * * @author Kim Shepherd - * @version $Revision$ */ public class IsWithdrawnCondition extends AbstractCondition { private final static Logger log = LogManager.getLogger(); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java index 4e30c75a2a36..e87c479de6b5 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValueMatchCondition.java @@ -23,7 +23,6 @@ * in a given metadata field * * @author Kim Shepherd - * @version $Revision$ */ public class MetadataValueMatchCondition extends AbstractCondition { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java index 74ccfa4ca842..c6ca9dfb9fa3 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/MetadataValuesMatchCondition.java @@ -23,7 +23,6 @@ * in a given metadata field * * @author Kim Shepherd - * @version $Revision$ */ public class MetadataValuesMatchCondition extends AbstractCondition { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java b/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java index 65f9925222c1..20138beb47ef 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/condition/ReadableByGroupCondition.java @@ -25,7 +25,6 @@ * can perform the action on a given item * * @author Kim Shepherd - * @version $Revision$ */ public class ReadableByGroupCondition extends AbstractCondition { private final static Logger log = LogManager.getLogger(); diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java index 99ece622f7a4..3882414def9b 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/AbstractOperator.java @@ -22,7 +22,6 @@ * as a logical result * * @author Kim Shepherd - * @version $Revision$ */ public abstract class AbstractOperator implements LogicalStatement { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java index 26606f209973..79bc5c381e4f 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/And.java @@ -19,7 +19,6 @@ * true if all sub-statements return true * * @author Kim Shepherd - * @version $Revision$ */ public class And extends AbstractOperator { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java index 1021ec6722ac..2a4b6823b6c9 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Nand.java @@ -18,7 +18,6 @@ * An operator that implements NAND by negating an AND operation * * @author Kim Shepherd - * @version $Revision$ */ public class Nand extends AbstractOperator { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java index 35c7bb22a7fb..277acdfd0153 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Not.java @@ -19,7 +19,6 @@ * Not can have one sub-statement only, while and, or, nor, ... can have multiple sub-statements. * * @author Kim Shepherd - * @version $Revision$ */ public class Not implements LogicalStatement { diff --git a/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java b/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java index 5110ac31bae3..e5697f8cc34c 100644 --- a/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java +++ b/dspace-api/src/main/java/org/dspace/content/logic/operator/Or.java @@ -19,7 +19,6 @@ * true if one or more sub-statements return true * * @author Kim Shepherd - * @version $Revision$ */ public class Or extends AbstractOperator { diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java index 568e4e25a533..23005b657508 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java @@ -106,6 +106,10 @@ void reserve(Context context, DSpaceObject dso, String identifier) void register(Context context, DSpaceObject dso) throws AuthorizeException, SQLException, IdentifierException; /** + * + * Register identifiers for a DSO, with a map of logical filters for each Identifier class to apply + * at the time of local registration. + * * @param context The relevant DSpace Context. * @param dso DSpace object to be registered * @param typeFilters If a service supports a given Identifier implementation, apply the associated filter @@ -117,6 +121,10 @@ void register(Context context, DSpaceObject dso, Map throws AuthorizeException, SQLException, IdentifierException; /** + * + * Register identifier(s) for the given DSO just with providers that support that Identifier class, and + * apply the given filter if that provider extends FilteredIdentifierProvider + * * @param context The relevant DSpace Context. * @param dso DSpace object to be registered * @param type Type of identifier to register @@ -129,6 +137,10 @@ void register(Context context, DSpaceObject dso, Class typ throws AuthorizeException, SQLException, IdentifierException; /** + * + * Register identifier(s) for the given DSO just with providers that support that Identifier class, and + * apply the given filter if that provider extends FilteredIdentifierProvider + * * @param context The relevant DSpace Context. * @param dso DSpace object to be registered * @param type Type of identifier to register From 943bdb19e07ed42ee1cd20f2a65a64b0e871b999 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 8 Feb 2023 17:15:22 +1300 Subject: [PATCH 301/394] [TL-249] Checkstyle fix --- .../org/dspace/identifier/DOIIdentifierProviderTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java index b71e93b8e061..d47586bcc076 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java @@ -759,7 +759,8 @@ public void testDelete_all_DOIs() } @Test - public void testUpdateMetadataSkippedForPending() throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + public void testUpdateMetadataSkippedForPending() + throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, WorkflowException { context.turnOffAuthorisationSystem(); Item item = newItem(); @@ -778,7 +779,8 @@ public void testUpdateMetadataSkippedForPending() throws SQLException, Authorize } @Test - public void testUpdateMetadataSkippedForMinted() throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + public void testUpdateMetadataSkippedForMinted() + throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, WorkflowException { context.turnOffAuthorisationSystem(); Item item = newItem(); @@ -797,7 +799,8 @@ public void testUpdateMetadataSkippedForMinted() throws SQLException, AuthorizeE } @Test - public void testLoadOrCreateDOIReturnsMintedStatus() throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + public void testLoadOrCreateDOIReturnsMintedStatus() + throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, WorkflowException { Item item = newItem(); // Mint a DOI without an explicit reserve or register context From c2845e95aa935c86436cbd40e29c3bd36b104cb0 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Thu, 9 Feb 2023 11:19:47 +0100 Subject: [PATCH 302/394] 99200: Delete items after copies have been requested fix --- .../requestitem/RequestItemServiceImpl.java | 6 +++++ .../app/requestitem/dao/RequestItemDAO.java | 4 +++ .../dao/impl/RequestItemDAOImpl.java | 9 +++++++ .../service/RequestItemService.java | 9 +++++++ .../org/dspace/content/ItemServiceImpl.java | 15 +++++++++++ .../content/service/ItemServiceTest.java | 26 +++++++++++++++++++ 6 files changed, 69 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java index d2b249f6ec9f..b915cfedd346 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemServiceImpl.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.Date; +import java.util.Iterator; import java.util.List; import org.apache.logging.log4j.LogManager; @@ -90,6 +91,11 @@ public RequestItem findByToken(Context context, String token) { } } + @Override + public Iterator findByItem(Context context, Item item) throws SQLException { + return requestItemDAO.findByItem(context, item); + } + @Override public void update(Context context, RequestItem requestItem) { try { diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java index 4a4ea6cd905d..b36ae58e0ca1 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/RequestItemDAO.java @@ -8,8 +8,10 @@ package org.dspace.app.requestitem.dao; import java.sql.SQLException; +import java.util.Iterator; import org.dspace.app.requestitem.RequestItem; +import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.core.GenericDAO; @@ -32,4 +34,6 @@ public interface RequestItemDAO extends GenericDAO { * @throws SQLException passed through. */ public RequestItem findByToken(Context context, String token) throws SQLException; + + public Iterator findByItem(Context context, Item item) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java index fa1ed9ffeb64..008174ded88c 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java @@ -8,6 +8,8 @@ package org.dspace.app.requestitem.dao.impl; import java.sql.SQLException; +import java.util.Iterator; +import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; @@ -15,6 +17,7 @@ import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.RequestItem_; import org.dspace.app.requestitem.dao.RequestItemDAO; +import org.dspace.content.Item; import org.dspace.core.AbstractHibernateDAO; import org.dspace.core.Context; @@ -39,4 +42,10 @@ public RequestItem findByToken(Context context, String token) throws SQLExceptio criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.token), token)); return uniqueResult(context, criteriaQuery, false, RequestItem.class); } + @Override + public Iterator findByItem(Context context, Item item) throws SQLException { + Query query = createQuery(context, "FROM RequestItem WHERE item_id= :uuid"); + query.setParameter("uuid", item.getID()); + return iterate(query); + } } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java b/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java index 5cab72e4e903..efac3b18bc7c 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/service/RequestItemService.java @@ -8,6 +8,7 @@ package org.dspace.app.requestitem.service; import java.sql.SQLException; +import java.util.Iterator; import java.util.List; import org.dspace.app.requestitem.RequestItem; @@ -62,6 +63,14 @@ public List findAll(Context context) */ public RequestItem findByToken(Context context, String token); + /** + * Retrieve a request based on the item. + * @param context current DSpace session. + * @param item the item to find requests for. + * @return the matching requests, or null if not found. + */ + public Iterator findByItem(Context context, Item item) throws SQLException; + /** * Save updates to the record. Only accept_request, and decision_date are set-able. * diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index ec4b964c8d26..4c8ec8794ae1 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -27,6 +27,8 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.dspace.app.requestitem.RequestItem; +import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.AuthorizeConfiguration; import org.dspace.authorize.AuthorizeException; @@ -158,6 +160,8 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) private ResearcherProfileService researcherProfileService; + @Autowired(required = true) + private RequestItemService requestItemService; protected ItemServiceImpl() { super(); @@ -780,6 +784,8 @@ protected void rawDelete(Context context, Item item) throws AuthorizeException, // remove version attached to the item removeVersion(context, item); + removeRequest(context, item); + removeOrcidSynchronizationStuff(context, item); // Also delete the item if it appears in a harvested collection. @@ -802,6 +808,15 @@ protected void rawDelete(Context context, Item item) throws AuthorizeException, itemDAO.delete(context, item); } + protected void removeRequest(Context context, Item item) throws SQLException { + Iterator requestItems = requestItemService.findByItem(context, item); + while (requestItems.hasNext()) { + RequestItem requestItem = requestItems.next(); + log.info("Remove Request Item: " + requestItem.getID()); + requestItemService.delete(context, requestItem); + } + } + @Override public void removeAllBundles(Context context, Item item) throws AuthorizeException, SQLException, IOException { Iterator bundles = item.getBundles().iterator(); diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java index f63e05ae3fab..579feaac3175 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java @@ -10,8 +10,11 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.sql.SQLException; import java.util.Comparator; import java.util.List; @@ -19,8 +22,10 @@ import org.apache.logging.log4j.Logger; import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.requestitem.RequestItem; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; +import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EntityTypeBuilder; @@ -28,7 +33,9 @@ import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipBuilder; import org.dspace.builder.RelationshipTypeBuilder; +import org.dspace.builder.RequestItemBuilder; import org.dspace.builder.ResourcePolicyBuilder; +import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.EntityType; @@ -554,6 +561,25 @@ public void testFindAndCountItemsWithAdminGroup() throws Exception { assertThat(result.size(), equalTo(1)); assertThat(count, equalTo(1)); } + + @Test + public void testRemoveItemThatHasRequests() throws Exception { + context.turnOffAuthorisationSystem(); + Item item = ItemBuilder.createItem(context, collection1) + .withTitle("Test") + .build(); + InputStream is = new ByteArrayInputStream(new byte[0]); + Bitstream bitstream = BitstreamBuilder.createBitstream(context, item, is) + .build(); + RequestItem requestItem = RequestItemBuilder.createRequestItem(context, item, bitstream) + .build(); + + itemService.delete(context, item); + context.dispatchEvents(); + context.restoreAuthSystemState(); + + assertNull(itemService.find(context, item.getID())); + } private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value, String authority, int place, MetadataValue metadataValue) { assertThat(metadataValue.getValue(), equalTo(value)); From 27f75e9fb18d55d15234fed124d7affc58af76be Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 9 Feb 2023 13:07:27 +0100 Subject: [PATCH 303/394] [CST-7756] added security check into 'subscription-send' script --- ...scriptionEmailNotificationServiceImpl.java | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java index 81648a92e3ef..8fb01cd36e92 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -9,6 +9,7 @@ import static org.dspace.core.Constants.COLLECTION; import static org.dspace.core.Constants.COMMUNITY; +import static org.dspace.core.Constants.READ; import java.sql.SQLException; import java.util.ArrayList; @@ -21,11 +22,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Subscription; import org.dspace.eperson.service.SubscribeService; import org.dspace.scripts.DSpaceRunnable; @@ -47,6 +51,8 @@ public class SubscriptionEmailNotificationServiceImpl implements SubscriptionEma @SuppressWarnings("rawtypes") private Map subscriptionType2generators = new HashMap<>(); + @Autowired + private AuthorizeService authorizeService; @Autowired private SubscribeService subscribeService; @@ -59,8 +65,8 @@ public SubscriptionEmailNotificationServiceImpl(Map @SuppressWarnings({ "rawtypes", "unchecked" }) public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency) { - List communities = new ArrayList<>(); - List collections = new ArrayList<>(); + List communityItems = new ArrayList<>(); + List collectionsItems = new ArrayList<>(); try { List subscriptions = findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, frequency); @@ -70,20 +76,29 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc int iterator = 0; for (Subscription subscription : subscriptions) { DSpaceObject dSpaceObject = subscription.getDSpaceObject(); + EPerson ePerson = subscription.getEPerson(); + + if (!authorizeService.authorizeActionBoolean(context, ePerson, dSpaceObject, READ, true)) { + iterator++; + continue; + } if (dSpaceObject.getType() == COMMUNITY) { - communities.addAll(contentUpdates.get(Community.class.getSimpleName().toLowerCase()) - .findUpdates(context, dSpaceObject, frequency)); + List indexableCommunityItems = contentUpdates + .get(Community.class.getSimpleName().toLowerCase()) + .findUpdates(context, dSpaceObject, frequency); + communityItems.addAll(getItems(context, ePerson, indexableCommunityItems)); } else if (dSpaceObject.getType() == COLLECTION) { - collections.addAll(contentUpdates.get(Collection.class.getSimpleName().toLowerCase()) - .findUpdates(context, dSpaceObject, frequency)); + List indexableCollectionItems = contentUpdates + .get(Collection.class.getSimpleName().toLowerCase()) + .findUpdates(context, dSpaceObject, frequency); + collectionsItems.addAll(getItems(context, ePerson, indexableCollectionItems)); } else { log.warn("found an invalid DSpace Object type ({}) among subscriptions to send", dSpaceObject.getType()); continue; } - var ePerson = subscription.getEPerson(); if (iterator < subscriptions.size() - 1) { // as the subscriptions are ordered by eperson id, so we send them by ePerson if (ePerson.equals(subscriptions.get(iterator + 1).getEPerson())) { @@ -91,14 +106,14 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc continue; } else { subscriptionType2generators.get(subscriptionType) - .notifyForSubscriptions(context, ePerson, communities, collections); - communities.clear(); - collections.clear(); + .notifyForSubscriptions(context, ePerson, communityItems, collectionsItems); + communityItems.clear(); + collectionsItems.clear(); } } else { //in the end of the iteration subscriptionType2generators.get(subscriptionType) - .notifyForSubscriptions(context, ePerson, communities, collections); + .notifyForSubscriptions(context, ePerson, communityItems, collectionsItems); } iterator++; } @@ -113,6 +128,19 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc } } + @SuppressWarnings("rawtypes") + private List getItems(Context context, EPerson ePerson, List indexableItems) + throws SQLException { + List items = new ArrayList(); + for (IndexableObject indexableitem : indexableItems) { + Item item = (Item) indexableitem.getIndexedObject(); + if (authorizeService.authorizeActionBoolean(context, ePerson, item, READ, true)) { + items.add(indexableitem); + } + } + return items; + } + /** * Return all Subscriptions by subscriptionType and frequency ordered by ePerson ID * if there are none it returns an empty list From 0bafbb60a42d776301a3bea93e51f36ce362d758 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 9 Feb 2023 11:03:24 +0100 Subject: [PATCH 304/394] Alternate way to restart workflow in case of decline --- .../org/dspace/workflow/WorkflowService.java | 4 ++ .../xmlworkflow/XmlWorkflowServiceImpl.java | 43 +++++++++++++++++++ .../processingaction/ProcessingAction.java | 8 ++-- .../SingleUserReviewAction.java | 6 +-- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java index 716b6cabd354..613c5821bcd1 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowService.java @@ -18,6 +18,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.xmlworkflow.WorkflowConfigurationException; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; /** * Service interface class for the WorkflowService framework. @@ -100,6 +101,9 @@ public WorkspaceItem sendWorkflowItemBackSubmission(Context c, T workflowItem, E String rejection_message) throws SQLException, AuthorizeException, IOException; + public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decliner, String provenance) + throws SQLException, AuthorizeException, IOException, WorkflowException; + public String getMyDSpaceLink(); public void deleteCollection(Context context, Collection collection) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java index fbe06245ab5b..36a6bb047399 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -1076,6 +1076,49 @@ public WorkspaceItem abort(Context c, XmlWorkflowItem wi, EPerson e) return wsi; } + @Override + public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decliner, String provenance) + throws SQLException, AuthorizeException, IOException, WorkflowException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException("You must be an admin to restart a workflow"); + } + context.turnOffAuthorisationSystem(); + + // rejection provenance + Item myitem = wi.getItem(); + + // Here's what happened + String provDescription = + provenance + " Declined by " + getEPersonName(decliner) + " on " + DCDate.getCurrent().toString() + + " (GMT) "; + + // Add to item as a DC field + itemService + .addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provDescription); + + //Clear any workflow schema related metadata + itemService + .clearMetadata(context, myitem, WorkflowRequirementsService.WORKFLOW_SCHEMA, Item.ANY, Item.ANY, Item.ANY); + + itemService.update(context, myitem); + + // remove policy for controller + removeUserItemPolicies(context, myitem, decliner); + revokeReviewerPolicies(context, myitem); + + // convert into personal workspace + WorkspaceItem wsi = returnToWorkspace(context, wi); + + log.info(LogHelper.getHeader(context, "decline_workflow", "workflow_item_id=" + + wi.getID() + "item_id=" + wi.getItem().getID() + "collection_id=" + wi.getCollection().getID() + + "eperson_id=" + decliner.getID())); + + // Restart workflow + this.startWithoutNotify(context, wsi); + context.restoreAuthSystemState(); + } + /** * Return the workflow item to the workspace of the submitter. The workflow * item is removed, and a workspace item created. diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java index 04509cf7a82d..7a1c62adbd1e 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ProcessingAction.java @@ -14,7 +14,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.service.ItemService; import org.dspace.core.Context; -import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; +import org.dspace.xmlworkflow.service.XmlWorkflowService; import org.dspace.xmlworkflow.state.actions.Action; import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; @@ -36,6 +36,8 @@ public abstract class ProcessingAction extends Action { protected ClaimedTaskService claimedTaskService; @Autowired(required = true) protected ItemService itemService; + @Autowired + protected XmlWorkflowService xmlWorkflowService; public static final String SUBMIT_EDIT_METADATA = "submit_edit_metadata"; public static final String SUBMIT_CANCEL = "submit_cancel"; @@ -73,8 +75,8 @@ public ActionResult processRejectPage(Context c, XmlWorkflowItem wfi, HttpServle // We have pressed reject, so remove the task the user has & put it back // to a workspace item - XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService().sendWorkflowItemBackSubmission(c, wfi, - c.getCurrentUser(), this.getProvenanceStartId(), reason); + xmlWorkflowService.sendWorkflowItemBackSubmission(c, wfi, c.getCurrentUser(), this.getProvenanceStartId(), + reason); return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java index ec42b408070b..a5357444c838 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java @@ -23,7 +23,6 @@ import org.dspace.eperson.EPerson; import org.dspace.workflow.WorkflowException; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; -import org.dspace.xmlworkflow.service.XmlWorkflowService; import org.dspace.xmlworkflow.state.Step; import org.dspace.xmlworkflow.state.actions.ActionResult; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -120,11 +119,8 @@ private ActionResult processDelete(Context c, XmlWorkflowItem wfi) */ private ActionResult processDecline(Context c, XmlWorkflowItem wfi) throws SQLException, IOException, AuthorizeException, WorkflowException { - EPerson user = c.getCurrentUser(); c.turnOffAuthorisationSystem(); - XmlWorkflowService xmlWorkflowService = XmlWorkflowServiceFactory.getInstance().getXmlWorkflowService(); - WorkspaceItem workspaceItem = xmlWorkflowService.abort(c, wfi, user); - xmlWorkflowService.start(c, workspaceItem); + xmlWorkflowService.restartWorkflow(c, wfi, c.getCurrentUser(), this.getProvenanceStartId()); c.restoreAuthSystemState(); return new ActionResult(ActionResult.TYPE.TYPE_SUBMISSION_PAGE); } From d8f5ae91bdd3db0af1d0e6d60f871cac1871b742 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 9 Feb 2023 14:59:53 +0100 Subject: [PATCH 305/394] Fix for decline when the delete doesn't persist in context before wfi is created again from same item --- .../java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java index 36a6bb047399..a60f36f6afc0 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -1110,6 +1110,10 @@ public void restartWorkflow(Context context, XmlWorkflowItem wi, EPerson decline // convert into personal workspace WorkspaceItem wsi = returnToWorkspace(context, wi); + // Because of issue of xmlWorkflowItemService not realising wfi wrapper has been deleted + context.commit(); + wsi = context.reloadEntity(wsi); + log.info(LogHelper.getHeader(context, "decline_workflow", "workflow_item_id=" + wi.getID() + "item_id=" + wi.getItem().getID() + "collection_id=" + wi.getCollection().getID() + "eperson_id=" + decliner.getID())); From 2331db335fdc48f700ed7f5e6451f21c3f5e0cfa Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 9 Feb 2023 15:26:30 +0100 Subject: [PATCH 306/394] [CST-7754] supervising user can perform operations on Workspace item only if they have proper permissions --- ...paceItemRestPermissionEvaluatorPlugin.java | 8 +++- .../SupervisionOrderRestRepositoryIT.java | 42 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java index 1b1a604b2094..c0efbd60f204 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java @@ -13,6 +13,7 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; @@ -45,6 +46,9 @@ public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermis @Autowired private SupervisionOrderService supervisionOrderService; + @Autowired + private AuthorizeService authorizeService; + @Override public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, DSpaceRestPermission permission) { @@ -89,7 +93,9 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t if (witem.getItem() != null) { if (supervisionOrderService.isSupervisor(context, ePerson, witem.getItem())) { - return true; + return authorizeService.authorizeActionBoolean(context, ePerson, witem.getItem(), + restPermission.getDspaceApiActionId(), + true); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java index bccfddda0acf..27b436b6bc76 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -698,6 +698,13 @@ public void createByAdminTest() throws Exception { .withPassword(password) .build(); + EPerson userC = + EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withEmail("test3@email.com") + .withPassword(password) + .build(); + parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") @@ -734,10 +741,17 @@ public void createByAdminTest() throws Exception { .addMember(userB) .build(); + Group groupC = + GroupBuilder.createGroup(context) + .withName("group C") + .addMember(userC) + .build(); + context.restoreAuthSystemState(); AtomicInteger supervisionOrderIdOne = new AtomicInteger(); AtomicInteger supervisionOrderIdTwo = new AtomicInteger(); + AtomicInteger supervisionOrderIdThree = new AtomicInteger(); String adminToken = getAuthToken(admin.getEmail(), password); @@ -759,12 +773,24 @@ public void createByAdminTest() throws Exception { .andDo(result -> supervisionOrderIdTwo .set(read(result.getResponse().getContentAsString(), "$.id"))); + getClient(adminToken).perform(post("/api/core/supervisionorders/") + .param("uuid", itemOne.getID().toString()) + .param("group", groupC.getID().toString()) + .param("type", "NONE") + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> supervisionOrderIdThree + .set(read(result.getResponse().getContentAsString(), "$.id"))); + SupervisionOrder supervisionOrderOne = supervisionOrderService.find(context, supervisionOrderIdOne.get()); SupervisionOrder supervisionOrderTwo = supervisionOrderService.find(context, supervisionOrderIdTwo.get()); + SupervisionOrder supervisionOrderThree = + supervisionOrderService.find(context, supervisionOrderIdThree.get()); + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrderOne.getID())) .andExpect(status().isOk()) .andExpect(jsonPath("$", @@ -775,8 +801,14 @@ public void createByAdminTest() throws Exception { .andExpect(jsonPath("$", matchSuperVisionOrder(context.reloadEntity(supervisionOrderTwo)))); + getClient(adminToken).perform(get("/api/core/supervisionorders/" + supervisionOrderThree.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + matchSuperVisionOrder(context.reloadEntity(supervisionOrderThree)))); + String authTokenA = getAuthToken(userA.getEmail(), password); String authTokenB = getAuthToken(userB.getEmail(), password); + String authTokenC = getAuthToken(userC.getEmail(), password); String patchBody = getPatchContent(List.of( new ReplaceOperation("/sections/traditionalpageone/dc.title/0", Map.of("value", "New Title")) @@ -808,12 +840,22 @@ public void createByAdminTest() throws Exception { "ExtraEntry") ))); + // supervisor of a NONE type cannot see workspace item + getClient(authTokenC).perform(get("/api/submission/workspaceitems/" + witem.getID())) + .andExpect(status().isForbidden()); + // update title of workspace item by userB is Forbidden getClient(authTokenB).perform(patch("/api/submission/workspaceitems/" + witem.getID()) .content(patchBody) .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) .andExpect(status().isForbidden()); + + // supervisor of a NONE type cannot patch workspace item + getClient(authTokenC).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isForbidden()); } @Test From 1fcbd6642c8973e827500e73a0aa398b5ece2b0b Mon Sep 17 00:00:00 2001 From: Mohamed Saber Eskander Date: Tue, 10 Jan 2023 16:09:41 +0100 Subject: [PATCH 307/394] [DURACOM-116] process sorted alphabetically --- .../rest/repository/ScriptRestRepository.java | 7 +++++- .../app/rest/ScriptRestRepositoryIT.java | 22 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index 13d5a560dc1f..a8b2ca1d5eaf 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -80,7 +81,11 @@ public ScriptRest findOne(Context context, String name) { @Override @PreAuthorize("hasAuthority('ADMIN')") public Page findAll(Context context, Pageable pageable) { - List scriptConfigurations = scriptService.getScriptConfigurations(context); + List scriptConfigurations = + scriptService.getScriptConfigurations(context) + .stream() + .sorted(Comparator.comparing(ScriptConfiguration::getName)) + .collect(Collectors.toList()); return converter.toRestPage(scriptConfigurations, pageable, utils.obtainProjection()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java index 0513a0d5f41e..07edfeec33d3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -10,6 +10,7 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; @@ -101,6 +102,25 @@ public void findAllScriptsTest() throws Exception { ))); } + @Test + public void findAllScriptsSortedAlphabeticallyTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/scripts") + .param("size", String.valueOf(scriptConfigurations.size()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.scripts", contains( + scriptConfigurations + .stream() + .sorted(Comparator.comparing(ScriptConfiguration::getName)) + .map(scriptConfiguration -> ScriptMatcher.matchScript( + scriptConfiguration.getName(), + scriptConfiguration.getDescription() + )) + .collect(Collectors.toList()) + ))); + } + @Test public void findAllScriptsUnauthorizedTest() throws Exception { @@ -115,7 +135,7 @@ public void findAllScriptsUnauthorizedTest() throws Exception { public void findAllScriptsPaginationTest() throws Exception { List alphabeticScripts = scriptConfigurations.stream() - .sorted(Comparator.comparing(s -> s.getClass().getName())) + .sorted(Comparator.comparing(ScriptConfiguration::getName)) .collect(Collectors.toList()); int totalPages = scriptConfigurations.size(); From 594050070f7afe01da7f0422f2c533f94e633b9e Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Thu, 9 Feb 2023 18:17:58 +0100 Subject: [PATCH 308/394] [TLC-249] Test abandoning of a pending DOI and minting afterwards --- .../identifier/DOIIdentifierProviderTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java index d47586bcc076..09387acd3ee3 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java +++ b/dspace-api/src/test/java/org/dspace/identifier/DOIIdentifierProviderTest.java @@ -9,7 +9,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeNotNull; @@ -778,6 +780,46 @@ public void testUpdateMetadataSkippedForPending() context.restoreAuthSystemState(); } + + @Test + public void testMintDoiAfterOrphanedPendingDOI() + throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, + WorkflowException { + context.turnOffAuthorisationSystem(); + Item item1 = newItem(); + // Mint a new DOI with PENDING status + String doi1 = this.createDOI(item1, DOIIdentifierProvider.PENDING, true); + // remove the item + itemService.delete(context, item1); + // Get the DOI from the service + DOI doi = doiService.findDOIByDSpaceObject(context, item1); + // ensure DOI has no state + assertNull("Orphaned DOI was not set deleted", doi); + // create a new item and a new DOI + Item item2 = newItem(); + String doi2 = null; + try { + // get a DOI (skipping any filters) + doi2 = provider.mint(context, item2); + } catch (IdentifierException e) { + e.printStackTrace(System.err); + fail("Got an IdentifierException: " + e.getMessage()); + } + + assertNotNull("Minted DOI is null?!", doi2); + assertFalse("Minted DOI is empty!", doi2.isEmpty()); + assertNotEquals("Minted DOI equals previously orphaned DOI.", doi1, doi2); + + try { + doiService.formatIdentifier(doi2); + } catch (DOIIdentifierException e) { + e.printStackTrace(System.err); + fail("Minted an unrecognizable DOI: " + e.getMessage()); + } + + context.restoreAuthSystemState(); + } + @Test public void testUpdateMetadataSkippedForMinted() throws SQLException, AuthorizeException, IOException, IdentifierException, IllegalAccessException, From 94c54503182b7fc88c437ea9d33f8f496e7f0ebc Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Thu, 9 Feb 2023 21:39:19 +0100 Subject: [PATCH 309/394] [TLC-249] Ensure property identifiers.item-status.register-doi is set --- dspace/config/modules/identifiers.cfg | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace/config/modules/identifiers.cfg b/dspace/config/modules/identifiers.cfg index 8577ee6a40c0..63a9cda30f17 100644 --- a/dspace/config/modules/identifiers.cfg +++ b/dspace/config/modules/identifiers.cfg @@ -41,9 +41,11 @@ # Show Register DOI button in item status page? # Default: false -#identifiers.item-status.register-doi = true +# This configuration property is exposed over rest. For dspace-angular to work, +# this property must always have a true or false value. Do not comment it out! +identifiers.item-status.register-doi = false # Which identifier types to show in submission step? # Default: handle, doi (currently the only supported identifier 'types') #identifiers.submission.display = handle -#identifiers.submission.display = doi \ No newline at end of file +#identifiers.submission.display = doi From 86d7902c1afe8caae7a40d1b2387c5e4f1977e58 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Fri, 10 Feb 2023 05:24:50 +0000 Subject: [PATCH 310/394] Added missing commit in cleanup method --- .../dspace/storage/bitstore/BitstreamStorageServiceImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java index 0bd71088da44..57d070f79720 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java @@ -303,6 +303,10 @@ public void cleanup(boolean deleteDbRecords, boolean verbose) throws SQLExceptio commitCounter++; if (commitCounter % 100 == 0) { context.dispatchEvents(); + // Commit actual changes to DB after dispatch events + System.out.print("Committing changes to the database..."); + context.commit(); + System.out.println(" Done!"); } context.uncacheEntity(bitstream); From 2208a6bc633fa99e0569737d9fdfc5da54c0ee48 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Fri, 10 Feb 2023 15:17:40 +0000 Subject: [PATCH 311/394] Update BitstreamStorageServiceImpl.java Amend commit log message --- .../dspace/storage/bitstore/BitstreamStorageServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java index 57d070f79720..b8a1a2e96ad4 100644 --- a/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/storage/bitstore/BitstreamStorageServiceImpl.java @@ -304,9 +304,9 @@ public void cleanup(boolean deleteDbRecords, boolean verbose) throws SQLExceptio if (commitCounter % 100 == 0) { context.dispatchEvents(); // Commit actual changes to DB after dispatch events - System.out.print("Committing changes to the database..."); + System.out.print("Performing incremental commit to the database..."); context.commit(); - System.out.println(" Done!"); + System.out.println(" Incremental commit done!"); } context.uncacheEntity(bitstream); From 3b1d930c508ca8d5e7351ef7c61a95a23275333a Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 13 Feb 2023 12:36:27 +0100 Subject: [PATCH 312/394] [CST-7756] Non-Discoverable and withdrawn items must be excluded from the notification list --- .../org/dspace/eperson/FrequencyType.java | 37 +++++++++++++++++ .../objectupdates/CollectionUpdates.java | 6 +-- .../objectupdates/CommunityUpdates.java | 6 +-- .../service/DSpaceObjectUpdates.java | 41 +++---------------- 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java index c658e30b12ad..72822fb8716e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java +++ b/dspace-api/src/main/java/org/dspace/eperson/FrequencyType.java @@ -7,7 +7,9 @@ */ package org.dspace.eperson; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Calendar; import org.apache.commons.codec.binary.StringUtils; @@ -28,6 +30,41 @@ private FrequencyType(String shortName) { this.shortName = shortName; } + public static String findLastFrequency(String frequency) { + String startDate = ""; + String endDate = ""; + Calendar cal = Calendar.getInstance(); + // Full ISO 8601 is e.g. + SimpleDateFormat fullIsoStart = new SimpleDateFormat("yyyy-MM-dd'T'00:00:00'Z'"); + SimpleDateFormat fullIsoEnd = new SimpleDateFormat("yyyy-MM-dd'T'23:59:59'Z'"); + switch (frequency) { + case "D": + cal.add(Calendar.DAY_OF_MONTH, -1); + endDate = fullIsoEnd.format(cal.getTime()); + startDate = fullIsoStart.format(cal.getTime()); + break; + case "M": + int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); + cal.add(Calendar.DAY_OF_MONTH, -dayOfMonth); + endDate = fullIsoEnd.format(cal.getTime()); + cal.add(Calendar.MONTH, -1); + cal.add(Calendar.DAY_OF_MONTH, 1); + startDate = fullIsoStart.format(cal.getTime()); + break; + case "W": + cal.add(Calendar.DAY_OF_WEEK, -1); + int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) - 1; + cal.add(Calendar.DAY_OF_WEEK, -dayOfWeek); + endDate = fullIsoEnd.format(cal.getTime()); + cal.add(Calendar.DAY_OF_WEEK, -6); + startDate = fullIsoStart.format(cal.getTime()); + break; + default: + return null; + } + return "[" + startDate + " TO " + endDate + "]"; + } + public static boolean isSupportedFrequencyType(String value) { for (FrequencyType ft : Arrays.asList(FrequencyType.values())) { if (StringUtils.equals(ft.getShortName(), value)) { diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java index 83ec59e4535b..12d056f36800 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CollectionUpdates.java @@ -10,13 +10,13 @@ import java.util.List; import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.FrequencyType; import org.dspace.subscriptions.service.DSpaceObjectUpdates; import org.springframework.beans.factory.annotation.Autowired; @@ -36,9 +36,9 @@ public class CollectionUpdates implements DSpaceObjectUpdates { public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) throws SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); - discoverQuery.addFilterQueries("search.resourcetype:" + Item.class.getSimpleName()); + getDefaultFilterQueries().stream().forEach(fq -> discoverQuery.addFilterQueries(fq)); discoverQuery.addFilterQueries("location.coll:(" + dSpaceObject.getID() + ")"); - discoverQuery.addFilterQueries("lastModified:" + findLastFrequency(frequency)); + discoverQuery.addFilterQueries("lastModified:" + FrequencyType.findLastFrequency(frequency)); DiscoverResult discoverResult = searchService.search(context, discoverQuery); return discoverResult.getIndexableObjects(); } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java index 335734d61cc1..0ae80d287aad 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/objectupdates/CommunityUpdates.java @@ -10,13 +10,13 @@ import java.util.List; import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchService; import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.FrequencyType; import org.dspace.subscriptions.service.DSpaceObjectUpdates; import org.springframework.beans.factory.annotation.Autowired; @@ -36,9 +36,9 @@ public class CommunityUpdates implements DSpaceObjectUpdates { public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) throws SearchServiceException { DiscoverQuery discoverQuery = new DiscoverQuery(); - discoverQuery.addFilterQueries("search.resourcetype:" + Item.class.getSimpleName()); + getDefaultFilterQueries().stream().forEach(fq -> discoverQuery.addFilterQueries(fq)); discoverQuery.addFilterQueries("location.comm:(" + dSpaceObject.getID() + ")"); - discoverQuery.addFilterQueries("lastModified:" + this.findLastFrequency(frequency)); + discoverQuery.addFilterQueries("lastModified:" + FrequencyType.findLastFrequency(frequency)); DiscoverResult discoverResult = searchService.search(context, discoverQuery); return discoverResult.getIndexableObjects(); } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java index 96de54f1ecfa..ec09b2a45fa4 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/service/DSpaceObjectUpdates.java @@ -7,11 +7,11 @@ */ package org.dspace.subscriptions.service; -import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.util.Arrays; import java.util.List; import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; import org.dspace.discovery.SearchServiceException; @@ -32,39 +32,10 @@ public interface DSpaceObjectUpdates { public List findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) throws SearchServiceException; - default String findLastFrequency(String frequency) { - String startDate = ""; - String endDate = ""; - Calendar cal = Calendar.getInstance(); - // Full ISO 8601 is e.g. - SimpleDateFormat fullIsoStart = new SimpleDateFormat("yyyy-MM-dd'T'00:00:00'Z'"); - SimpleDateFormat fullIsoEnd = new SimpleDateFormat("yyyy-MM-dd'T'23:59:59'Z'"); - switch (frequency) { - case "D": - cal.add(Calendar.DAY_OF_MONTH, -1); - endDate = fullIsoEnd.format(cal.getTime()); - startDate = fullIsoStart.format(cal.getTime()); - break; - case "M": - int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); - cal.add(Calendar.DAY_OF_MONTH, -dayOfMonth); - endDate = fullIsoEnd.format(cal.getTime()); - cal.add(Calendar.MONTH, -1); - cal.add(Calendar.DAY_OF_MONTH, 1); - startDate = fullIsoStart.format(cal.getTime()); - break; - case "W": - cal.add(Calendar.DAY_OF_WEEK, -1); - int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) - 1; - cal.add(Calendar.DAY_OF_WEEK, -dayOfWeek); - endDate = fullIsoEnd.format(cal.getTime()); - cal.add(Calendar.DAY_OF_WEEK, -6); - startDate = fullIsoStart.format(cal.getTime()); - break; - default: - return null; - } - return "[" + startDate + " TO " + endDate + "]"; + default List getDefaultFilterQueries() { + return Arrays.asList("search.resourcetype:" + Item.class.getSimpleName(), + "-discoverable:" + false, + "-withdrawn:" + true); } } From a37403781e21ed2dfe676646c6575e92bf014085 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Mon, 13 Feb 2023 12:53:13 +0100 Subject: [PATCH 313/394] [DURACOM-116] moved sorting to service --- .../src/main/java/org/dspace/scripts/ScriptServiceImpl.java | 5 ++++- .../dspace/app/rest/repository/ScriptRestRepository.java | 6 +----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java index 4eb7cdbbc164..c8a7812a5159 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java @@ -8,6 +8,7 @@ package org.dspace.scripts; import java.lang.reflect.InvocationTargetException; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -36,7 +37,9 @@ public ScriptConfiguration getScriptConfiguration(String name) { @Override public List getScriptConfigurations(Context context) { return serviceManager.getServicesByType(ScriptConfiguration.class).stream().filter( - scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context)).collect(Collectors.toList()); + scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context)) + .sorted(Comparator.comparing(ScriptConfiguration::getName)) + .collect(Collectors.toList()); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java index a8b2ca1d5eaf..d974a6d78a6c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -11,7 +11,6 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -82,10 +81,7 @@ public ScriptRest findOne(Context context, String name) { @PreAuthorize("hasAuthority('ADMIN')") public Page findAll(Context context, Pageable pageable) { List scriptConfigurations = - scriptService.getScriptConfigurations(context) - .stream() - .sorted(Comparator.comparing(ScriptConfiguration::getName)) - .collect(Collectors.toList()); + scriptService.getScriptConfigurations(context); return converter.toRestPage(scriptConfigurations, pageable, utils.obtainProjection()); } From a8a886b242416a766f7c5de2649984a8d1755b84 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 14 Feb 2023 11:22:13 +0100 Subject: [PATCH 314/394] removed the log info --- dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 4c8ec8794ae1..2c0ca9b1bb29 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -812,7 +812,6 @@ protected void removeRequest(Context context, Item item) throws SQLException { Iterator requestItems = requestItemService.findByItem(context, item); while (requestItems.hasNext()) { RequestItem requestItem = requestItems.next(); - log.info("Remove Request Item: " + requestItem.getID()); requestItemService.delete(context, requestItem); } } From 542de150730e52a7f7e6d2dfa21b69d525c94923 Mon Sep 17 00:00:00 2001 From: Oliver Goldschmidt Date: Wed, 15 Feb 2023 16:37:29 +0100 Subject: [PATCH 315/394] introduces type mapping to COAR types for Scopus --- ...er-scopusToCoarPublicationTypes.properties | 14 ++++++++++++++ dspace/config/spring/api/crosswalks.xml | 7 +++++++ .../config/spring/api/scopus-integration.xml | 19 ++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 dspace/config/crosswalks/mapConverter-scopusToCoarPublicationTypes.properties diff --git a/dspace/config/crosswalks/mapConverter-scopusToCoarPublicationTypes.properties b/dspace/config/crosswalks/mapConverter-scopusToCoarPublicationTypes.properties new file mode 100644 index 000000000000..35e759cd6860 --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-scopusToCoarPublicationTypes.properties @@ -0,0 +1,14 @@ +ar = Resource Types::text::journal::journal article +er = Resource Types::text::journal::journal article::corrigendum +re = Resource Types::text::journal::journal article::review article +cp = Resource Types::text::conference outputs::conference proceedings::conference paper +bk = Resource Types::text::book +ch = Resource Types::text::book chapter +ed = Resource Types::text::journal::editorial +le = Resource Types::text::letter +cr = Conference Review +ab = Abstract Report +bz = Business Article +no = Note +pr = Press Release +sh = Short Survey \ No newline at end of file diff --git a/dspace/config/spring/api/crosswalks.xml b/dspace/config/spring/api/crosswalks.xml index d8bc01be9e01..a7f70d968dd8 100644 --- a/dspace/config/spring/api/crosswalks.xml +++ b/dspace/config/spring/api/crosswalks.xml @@ -523,6 +523,7 @@ + @@ -596,6 +597,12 @@ + + + + + + diff --git a/dspace/config/spring/api/scopus-integration.xml b/dspace/config/spring/api/scopus-integration.xml index 66d4ab453410..16347cef8994 100644 --- a/dspace/config/spring/api/scopus-integration.xml +++ b/dspace/config/spring/api/scopus-integration.xml @@ -19,7 +19,6 @@ - @@ -32,6 +31,8 @@ + + @@ -199,6 +200,22 @@ + + + + + + + + + + + + + + + + From 9e8276deba244be07123be49144611c9f611ff09 Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Wed, 15 Feb 2023 19:02:39 +0100 Subject: [PATCH 316/394] CST-8936 fix permission for bitstream generated by media-filter --- .../app/mediafilter/MediaFilterServiceImpl.java | 4 ++-- .../org/dspace/authorize/AuthorizeServiceImpl.java | 9 +++++++++ .../dspace/authorize/service/AuthorizeService.java | 13 +++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java index 50efa68ff410..6b7f833e6dde 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java @@ -397,8 +397,8 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo Group anonymous = groupService.findByName(context, Group.ANONYMOUS); authorizeService.addPolicy(context, b, Constants.READ, anonymous); } else { - //- Inherit policies from the source bitstream - authorizeService.inheritPolicies(context, source, b); + //- replace the policies using the same in the source bitstream + authorizeService.replaceAllPolicies(context, source, b); } //do post-processing of the generated bitstream diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index 5dd491fd4d3e..34543c078ae2 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -523,6 +523,15 @@ public void inheritPolicies(Context c, DSpaceObject src, addPolicies(c, nonAdminPolicies, dest); } + @Override + public void replaceAllPolicies(Context context, DSpaceObject source, DSpaceObject dest) + throws SQLException, AuthorizeException { + // find all policies for the source object + List policies = getPolicies(context, source); + removeAllPolicies(context, dest); + addPolicies(context, policies, dest); + } + @Override public void switchPoliciesAction(Context context, DSpaceObject dso, int fromAction, int toAction) throws SQLException, AuthorizeException { diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java index 6b097cdd7331..36679f94c6a4 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java @@ -600,4 +600,17 @@ long countAdminAuthorizedCollection(Context context, String query) * @return true if the current user can manage accounts */ boolean isAccountManager(Context context); + + /** + * Replace all the policies in the target object with exactly the same policies that exist in the source object + * + * @param context DSpace Context + * @param source source of policies + * @param dest destination of inherited policies + * @throws SQLException if there's a database problem + * @throws AuthorizeException if the current user is not authorized to add these policies + */ + public void replaceAllPolicies(Context context, DSpaceObject source, DSpaceObject dest) + throws SQLException, AuthorizeException; + } From 055e5a3d09ba2de2c9ca64623665191ca6467417 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 17 Feb 2023 09:42:40 -0600 Subject: [PATCH 317/394] Updates to LICENSES_THIRD_PARTY for 7.5 --- LICENSES_THIRD_PARTY | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 92d0b71a70af..b96ea77648a6 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -30,9 +30,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * parso (com.epam:parso:2.0.14 - https://github.com/epam/parso) * Esri Geometry API for Java (com.esri.geometry:esri-geometry-api:2.2.0 - https://github.com/Esri/geometry-api-java) * ClassMate (com.fasterxml:classmate:1.3.0 - http://github.com/cowtowncoder/java-classmate) - * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.12.6 - http://github.com/FasterXML/jackson) - * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.12.6 - https://github.com/FasterXML/jackson-core) - * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.12.6.1 - http://github.com/FasterXML/jackson) + * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.13.4 - http://github.com/FasterXML/jackson) + * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.13.4 - https://github.com/FasterXML/jackson-core) + * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.13.4.2 - http://github.com/FasterXML/jackson) * Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.6 - http://github.com/FasterXML/jackson-dataformats-binary) * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.12.3 - http://github.com/FasterXML/jackson-dataformats-binary) * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.1 - https://github.com/FasterXML/jackson-dataformats-text) @@ -151,7 +151,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org) * Apache Ant Core (org.apache.ant:ant:1.10.11 - https://ant.apache.org/) * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.11 - https://ant.apache.org/) - * Apache Commons BCEL (org.apache.bcel:bcel:6.4.0 - https://commons.apache.org/proper/commons-bcel) + * Apache Commons BCEL (org.apache.bcel:bcel:6.6.0 - https://commons.apache.org/proper/commons-bcel) * Calcite Core (org.apache.calcite:calcite-core:1.27.0 - https://calcite.apache.org) * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.27.0 - https://calcite.apache.org) * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.18.0 - https://calcite.apache.org/avatica) @@ -159,12 +159,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Apache Commons Compress (org.apache.commons:commons-compress:1.21 - https://commons.apache.org/proper/commons-compress/) * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.8.0 - https://commons.apache.org/proper/commons-configuration/) * Apache Commons CSV (org.apache.commons:commons-csv:1.9.0 - https://commons.apache.org/proper/commons-csv/) - * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.8.0 - https://commons.apache.org/dbcp/) + * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.9.0 - https://commons.apache.org/dbcp/) * Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/) * Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/) * Apache Commons Math (org.apache.commons:commons-math3:3.6.1 - http://commons.apache.org/proper/commons-math/) - * Apache Commons Pool (org.apache.commons:commons-pool2:2.9.0 - https://commons.apache.org/proper/commons-pool/) - * Apache Commons Text (org.apache.commons:commons-text:1.9 - https://commons.apache.org/proper/commons-text) + * Apache Commons Pool (org.apache.commons:commons-pool2:2.11.1 - https://commons.apache.org/proper/commons-pool/) + * Apache Commons Text (org.apache.commons:commons-text:1.10.0 - https://commons.apache.org/proper/commons-text) * Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client) * Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework) * Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes) @@ -218,10 +218,10 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.1 - https://lucene.apache.org/lucene-parent/lucene-suggest) - * Apache FontBox (org.apache.pdfbox:fontbox:2.0.24 - http://pdfbox.apache.org/) + * Apache FontBox (org.apache.pdfbox:fontbox:2.0.27 - http://pdfbox.apache.org/) * PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.3 - https://www.apache.org/jbig2-imageio/) * Apache JempBox (org.apache.pdfbox:jempbox:1.8.16 - http://www.apache.org/pdfbox-parent/jempbox/) - * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.24 - https://www.apache.org/pdfbox-parent/pdfbox/) + * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.27 - https://www.apache.org/pdfbox-parent/pdfbox/) * Apache PDFBox Debugger (org.apache.pdfbox:pdfbox-debugger:2.0.25 - https://www.apache.org/pdfbox-parent/pdfbox-debugger/) * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.25 - https://www.apache.org/pdfbox-parent/pdfbox-tools/) * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.25 - https://www.apache.org/pdfbox-parent/xmpbox/) @@ -426,7 +426,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * asm-commons (org.ow2.asm:asm-commons:8.0.1 - http://asm.ow2.io/) * asm-tree (org.ow2.asm:asm-tree:7.1 - http://asm.ow2.org/) * asm-util (org.ow2.asm:asm-util:7.1 - http://asm.ow2.org/) - * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.4.1 - https://jdbc.postgresql.org) + * PostgreSQL JDBC Driver (org.postgresql:postgresql:42.4.3 - https://jdbc.postgresql.org) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/) @@ -589,7 +589,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.6.0 - https://www.webjars.org) * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.10 - https://www.webjars.org) * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.1 - https://www.webjars.org) - * core-js (org.webjars.npm:core-js:3.25.2 - https://www.webjars.org) + * core-js (org.webjars.npm:core-js:3.28.0 - https://www.webjars.org) * @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.6.1 - https://www.webjars.org) Mozilla Public License: From 654520e1bb3d3c0eeba8ca1393936d0bf7a21086 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 17 Feb 2023 10:12:23 -0600 Subject: [PATCH 318/394] [maven-release-plugin] prepare release dspace-7.5 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index b277bf058469..b02778f7bbcf 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 879fb1da6955..b79e3954f931 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 59bcdf39a2df..0bd38a139043 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 6dbe3f346681..0a4aa46cbd35 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index f4df15ab1c83..df97a13ffc99 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - 7.5-SNAPSHOT + 7.5 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index a17a2d73b68a..251f36c026af 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 5eabcca707b3..362027d3da15 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 3a0aa6df5cda..77c3fdb4c4a0 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 51d5dc2df363..4b20c40898a9 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 8ecdd8344f13..e71cb6e585a3 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index a2ca42b9cba4..45e78a39d787 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 97ddb4a808b4..b1a51b33be96 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 8c738b35f081..ce301196793e 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - 7.5-SNAPSHOT + 7.5 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index cbf2e10c33e2..3198433c8d71 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - 7.5-SNAPSHOT + 7.5 ../pom.xml diff --git a/pom.xml b/pom.xml index c66f46c9d7c4..5fc99b9081be 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - 7.5-SNAPSHOT + 7.5 DSpace Parent Project DSpace open source software is a turnkey institutional repository application. @@ -872,14 +872,14 @@ org.dspace dspace-rest - 7.5-SNAPSHOT + 7.5 jar classes org.dspace dspace-rest - 7.5-SNAPSHOT + 7.5 war @@ -1030,69 +1030,69 @@ org.dspace dspace-api - 7.5-SNAPSHOT + 7.5 org.dspace dspace-api test-jar - 7.5-SNAPSHOT + 7.5 test org.dspace.modules additions - 7.5-SNAPSHOT + 7.5 org.dspace dspace-sword - 7.5-SNAPSHOT + 7.5 org.dspace dspace-swordv2 - 7.5-SNAPSHOT + 7.5 org.dspace dspace-oai - 7.5-SNAPSHOT + 7.5 org.dspace dspace-services - 7.5-SNAPSHOT + 7.5 org.dspace dspace-server-webapp test-jar - 7.5-SNAPSHOT + 7.5 test org.dspace dspace-rdf - 7.5-SNAPSHOT + 7.5 org.dspace dspace-iiif - 7.5-SNAPSHOT + 7.5 org.dspace dspace-server-webapp - 7.5-SNAPSHOT + 7.5 jar classes org.dspace dspace-server-webapp - 7.5-SNAPSHOT + 7.5 war @@ -1932,7 +1932,7 @@ scm:git:git@github.com:DSpace/DSpace.git scm:git:git@github.com:DSpace/DSpace.git git@github.com:DSpace/DSpace.git - HEAD + dspace-7.5 From f4b90945e5004df8f69f38aa51bc3a2ae7225f39 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 6 Mar 2023 18:18:26 +0100 Subject: [PATCH 319/394] [DSC-573] Skipped hidden submission step during conversion --- .../dspace/app/util/SubmissionStepConfig.java | 29 +++++ .../dspaceFolder/config/item-submission.xml | 36 +++++- .../config/spring/api/edititem-service.xml | 9 ++ .../dspaceFolder/config/submission-forms.xml | 29 ++++- .../converter/AInprogressItemConverter.java | 4 + .../app/rest/converter/EditItemConverter.java | 4 + .../rest/AuthorizationRestRepositoryIT.java | 49 ++++++- .../org/dspace/app/rest/CorrectionStepIT.java | 121 ++++++------------ .../app/rest/EditItemRestRepositoryIT.java | 30 +++++ .../SubmissionDefinitionsControllerIT.java | 16 +-- .../app/rest/SubmissionFormsControllerIT.java | 4 +- .../rest/SubmissionSectionsControllerIT.java | 3 +- .../rest/WorkflowItemRestRepositoryIT.java | 31 +++++ .../rest/WorkspaceItemRestRepositoryIT.java | 32 +++++ dspace/config/item-submission.xml | 1 - 15 files changed, 301 insertions(+), 97 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java index 7f29e6d79e52..7c364805c300 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java @@ -11,6 +11,9 @@ import java.util.Map; import org.apache.commons.lang3.BooleanUtils; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.WorkspaceItem; +import org.hibernate.proxy.HibernateProxyHelper; /** * Class representing configuration for a single step within an Item Submission @@ -179,6 +182,32 @@ public String getVisibilityOutside() { return visibilityOutside; } + public boolean isHiddenForInProgressSubmission(InProgressSubmission obj) { + + String scopeToCheck = getScope(obj); + + if (scope == null || scopeToCheck == null) { + return false; + } + + String visibility = getVisibility(); + String visibilityOutside = getVisibilityOutside(); + + if (scope.equalsIgnoreCase(scopeToCheck)) { + return "hidden".equalsIgnoreCase(visibility); + } else { + return visibilityOutside == null || "hidden".equalsIgnoreCase(visibilityOutside); + } + + } + + private String getScope(InProgressSubmission obj) { + if (HibernateProxyHelper.getClassWithoutInitializingProxy(obj).equals(WorkspaceItem.class)) { + return "submission"; + } + return "workflow"; + } + /** * Get the number of this step in the current Submission process config. * Step numbers start with #0 (although step #0 is ALWAYS the special diff --git a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml index 15dcf40ab3a8..0bd6248d75f6 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -57,7 +57,6 @@ org.dspace.app.rest.submit.step.CollectionStep collection - submission submit.progressbar.describe.stepone @@ -245,6 +244,34 @@ correction workflow + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + workflow + + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + submission + + + + + org.dspace.app.rest.submit.step.CollectionStep + collection + + + + + org.dspace.app.rest.submit.step.CollectionStep + collection + submission + + @@ -305,6 +332,13 @@ + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml index a919a1c9fcbc..5320984372d3 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml @@ -89,6 +89,15 @@ + + + + + ADMIN + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml index d13811b144ce..482abb4adb4a 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml @@ -1946,7 +1946,34 @@ You can leave out the day and/or month if they aren't applicable. - + +

+ + + dc + title + + false + + onebox + Field required + + +
+ +
+ + + dc + type + + false + + onebox + Field required + + +
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index 74a3c7264e75..248342e1b19b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -101,6 +101,10 @@ protected void fillFromModel(T obj, R witem, Projection projection) { for (SubmissionSectionRest sections : def.getPanels()) { SubmissionStepConfig stepConfig = submissionSectionConverter.toModel(sections); + if (stepConfig.isHiddenForInProgressSubmission(obj)) { + continue; + } + /* * First, load the step processing class (using the current * class loader) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EditItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EditItemConverter.java index eb2dedef3e62..24c8761268c5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EditItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EditItemConverter.java @@ -101,6 +101,10 @@ protected void fillFromModel(EditItem obj, EditItemRest rest, Projection project for (SubmissionSectionRest sections : def.getPanels()) { SubmissionStepConfig stepConfig = submissionSectionConverter.toModel(sections); + if (stepConfig.isHiddenForInProgressSubmission(obj)) { + continue; + } + /* * First, load the step processing class (using the current * class loader) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java index 350e471f49c5..67185f2cdab2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java @@ -25,7 +25,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.IOException; import java.io.Serializable; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -64,6 +66,7 @@ import org.dspace.app.rest.projection.DefaultProjection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; @@ -81,8 +84,13 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; +import org.dspace.xmlworkflow.storedcomponents.PoolTask; +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; +import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; +import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; import org.hamcrest.Matcher; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -117,6 +125,12 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration @Autowired private ItemConverter itemConverter; + @Autowired + private PoolTaskService poolTaskService; + + @Autowired + private XmlWorkflowItemService xmlWorkflowItemService; + @Autowired private Utils utils; private SiteService siteService; @@ -175,6 +189,14 @@ public void setUp() throws Exception { configurationService.setProperty("webui.user.assumelogin", true); } + @After + public void cleanUp() throws Exception { + context.turnOffAuthorisationSystem(); + poolTaskService.findAll(context).forEach(this::deletePoolTask); + xmlWorkflowItemService.findAll(context).forEach(this::deleteWorkflowItem); + context.restoreAuthSystemState(); + } + @Test /** * This method is not implemented @@ -2824,7 +2846,17 @@ public void verifySpecialGroupForNonAdministrativeUsersTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.errors").doesNotExist()); - getClient(epersonToken).perform(get("/api/submission/workspaceitems/" + workspaceItemIdRef.get())) + AtomicReference workflowItemIdRef = new AtomicReference(); + + getClient(epersonToken).perform(post("/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + workspaceItemIdRef.get()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()) + .andDo(result -> workflowItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + getClient(tokenAdmin).perform(get("/api/workflow/workflowitems/" + workflowItemIdRef.get())) .andExpect(status().isOk()) .andExpect(jsonPath("$.sections.correction.metadata", hasSize(equalTo(3)))) .andExpect(jsonPath("$.sections.correction.empty", is(false))) @@ -2931,5 +2963,20 @@ private String getAuthorizationID(String epersonUuid, String featureName, String + id.toString(); } + private void deletePoolTask(PoolTask poolTask) { + try { + poolTaskService.delete(context, poolTask); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e); + } + } + + private void deleteWorkflowItem(XmlWorkflowItem workflowItem) { + try { + xmlWorkflowItemService.delete(context, workflowItem); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java index 05b3db7a4ac6..06f85d5aa0b5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java @@ -20,6 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; @@ -40,21 +41,17 @@ import org.dspace.builder.EntityTypeBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.RelationshipTypeBuilder; -import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Collection; import org.dspace.content.EntityType; import org.dspace.content.Item; -import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.EntityTypeService; -import org.dspace.content.service.ItemService; import org.dspace.content.service.RelationshipService; import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; -import org.dspace.eperson.EPerson; import org.dspace.services.ConfigurationService; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -68,7 +65,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; -import org.springframework.data.rest.webmvc.RestMediaTypes; /** * Integration tests for {@link CorrectionStep}. @@ -84,9 +80,6 @@ public class CorrectionStepIT extends AbstractControllerIntegrationTest { @Autowired private EntityTypeService entityTypeService; - @Autowired - private ItemService itemService; - @Autowired private PoolTaskService poolTaskService; @@ -191,14 +184,12 @@ public void destroy() throws Exception { } } - if (workspaceItemIdRef.get() != null) { - WorkspaceItemBuilder.deleteWorkspaceItem(workspaceItemIdRef.get()); - } poolTaskService.findAll(context).forEach(this::deletePoolTask); - super.destroy(); + xmlWorkflowItemService.findAll(context).forEach(this::deleteWorkflowItem); super.destroy(); } + @Test public void checkCorrection() throws Exception { @@ -213,8 +204,6 @@ public void checkCorrection() throws Exception { .andExpect(status().isCreated()) .andDo(result -> workspaceItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - Integer workspaceItemId = workspaceItemIdRef.get(); - List relationshipList = relationshipService.findByItem(context, itemToBeCorrected); assert (relationshipList.size() > 0); Item correctedItem = relationshipList.get(0).getLeftItem(); @@ -241,8 +230,9 @@ public void checkCorrection() throws Exception { .contentType("application/json-patch+json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.errors").doesNotExist()); + //add an asbtract description - Map addValue = new HashMap(); + Map addValue = new HashMap(); addValue.put("value","Description Test"); addGrant = new ArrayList(); addGrant.add(new AddOperation("/sections/traditionalpagetwo/dc.description.abstract", List.of(addValue))); @@ -252,8 +242,23 @@ public void checkCorrection() throws Exception { .contentType("application/json-patch+json")) .andExpect(status().isOk()) .andExpect(jsonPath("$.errors").doesNotExist()); - //check if the correction is present + getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + newWorkspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.correction.metadata").doesNotExist()); + + AtomicReference workflowItemIdRef = new AtomicReference(); + + getClient(tokenSubmitter).perform(post("/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + newWorkspaceItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()) + .andDo(result -> workflowItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + //check if the correction is present + getClient(tokenAdmin).perform(get("/api/workflow/workflowitems/" + workflowItemIdRef.get())) //The status has to be 200 OK .andExpect(status().isOk()) //The array of browse index should have a size equals to 4 @@ -279,14 +284,23 @@ public void checkEmptyCorrection() throws Exception { .andExpect(status().isCreated()) .andDo(result -> workspaceItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - Integer workspaceItemId = workspaceItemIdRef.get(); List relationshipList = relationshipService.findByItem(context, itemToBeCorrected); assert (relationshipList.size() > 0); Item correctedItem = relationshipList.get(0).getLeftItem(); WorkspaceItem newWorkspaceItem = workspaceItemService.findByItem(context, correctedItem); + AtomicReference workflowItemIdRef = new AtomicReference(); + + getClient(tokenSubmitter).perform(post("/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + newWorkspaceItem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()) + .andDo(result -> workflowItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + //check if the correction section is empty on relation item - getClient(tokenSubmitter).perform(get("/api/submission/workspaceitems/" + newWorkspaceItem.getID())) + getClient(tokenAdmin).perform(get("/api/workflow/workflowitems/" + workflowItemIdRef.get())) //The status has to be 200 OK .andExpect(status().isOk()) //The array of browse index should have a size greater or equals to 1 @@ -295,74 +309,12 @@ public void checkEmptyCorrection() throws Exception { } - private static Matcher matchMetadataCorrection(String value) { + private static Matcher matchMetadataCorrection(String value) { return Matchers.anyOf( - // Check workspaceitem properties hasJsonPath("$.newValues[0]", is(value)), hasJsonPath("$.oldValues[0]", is(value))); } - private void claimTaskAndCheckResponse(String authToken, Integer poolTaskId) throws SQLException, Exception { - getClient(authToken).perform(post("/api/workflow/claimedtasks") - .contentType(RestMediaTypes.TEXT_URI_LIST) - .content("/api/workflow/pooltasks/" + poolTaskId)) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$", Matchers.allOf(hasJsonPath("$.type", is("claimedtask"))))); - } - - private XmlWorkflowItem setSubmission(EPerson user, String title, String date) - throws Exception { - - context.setCurrentUser(user); - - AtomicReference idRef = new AtomicReference(); - String tokenSubmitter = getAuthToken(user.getEmail(), password); - // create empty workSpaceItem - getClient(tokenSubmitter).perform(post("/api/submission/workspaceitems") - .param("owningCollection", collection.getID().toString()) - .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) - .andExpect(status().isCreated()) - .andDo((result -> idRef - .set(read(result.getResponse().getContentAsString(), "$.id")))); - - WorkspaceItem witem = workspaceItemService.find(context, idRef.get()); - Item item = witem.getItem(); - - // add metadata - itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), - "title", null, null, title); - itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), - "date", "issued", null, date); - itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), - "subject", null, null, "ExtraEntry"); - itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), - "type", null, null, "text"); - // accept license - List addGrant = new ArrayList(); - addGrant.add(new AddOperation("/sections/license/granted", true)); - String patchBody = getPatchContent(addGrant); - getClient(tokenSubmitter).perform(patch("/api/submission/workspaceitems/" + witem.getID()) - .content(patchBody) - .contentType("application/json-patch+json")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.errors").doesNotExist()) - .andExpect(jsonPath("$.sections.license.granted", - is(true))) - .andExpect(jsonPath("$.sections.license.acceptanceDate").isNotEmpty()) - .andExpect(jsonPath("$.sections.license.url").isNotEmpty()); - - //deposit workSpaceItem, so it become workFlowItem - getClient(tokenSubmitter).perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") - .content("/api/submission/workspaceitems/" + witem.getID()) - .contentType(textUriContentType)) - .andExpect(status().isCreated()) - .andDo((result -> idRef - .set(read(result.getResponse().getContentAsString(), "$.id")))); - - XmlWorkflowItem xmlWorkFlowItem = xmlWorkflowItemService.find(context, idRef.get()); - return xmlWorkFlowItem; - } - private void deletePoolTask(PoolTask poolTask) { try { poolTaskService.delete(context, poolTask); @@ -371,5 +323,12 @@ private void deletePoolTask(PoolTask poolTask) { } } + private void deleteWorkflowItem(XmlWorkflowItem workflowItem) { + try { + xmlWorkflowItemService.delete(context, workflowItem); + } catch (SQLException | AuthorizeException | IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java index 22ddf83ac195..cfcc684d019c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java @@ -1674,6 +1674,36 @@ public void testBitstreamResourcePoliciesUpdateWithAppendMode() throws Exception } + @Test + public void testEditWithHiddenSections() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withEntityType("Publication") + .build(); + + Item item = ItemBuilder.createItem(context, collection) + .withTitle("Item") + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/core/edititems/" + item.getID() + ":MODE-TEST-HIDDEN")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.test-outside-workflow-hidden").exists()) + .andExpect(jsonPath("$.sections.test-outside-submission-hidden").doesNotExist()) + .andExpect(jsonPath("$.sections.test-never-hidden").exists()) + .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); + + } + private Bitstream getBitstream(Item item, String name) throws SQLException { return bitstreamService.getBitstreamByName(item, "ORIGINAL", name); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index 5214bce60a79..f5313621c67f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -310,7 +310,7 @@ public void findAllPaginationTest() throws Exception { .param("page", "0")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("patent"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("test-hidden"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) @@ -322,10 +322,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=13"), Matchers.containsString("size=1")))) + Matchers.containsString("page=14"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(14))) - .andExpect(jsonPath("$.page.totalPages", is(14))) + .andExpect(jsonPath("$.page.totalElements", is(15))) + .andExpect(jsonPath("$.page.totalPages", is(15))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -333,7 +333,7 @@ public void findAllPaginationTest() throws Exception { .param("page", "1")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("accessConditionNotDiscoverable"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("patent"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) @@ -348,10 +348,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=13"), Matchers.containsString("size=1")))) + Matchers.containsString("page=14"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(14))) - .andExpect(jsonPath("$.page.totalPages", is(14))) + .andExpect(jsonPath("$.page.totalElements", is(15))) + .andExpect(jsonPath("$.page.totalPages", is(15))) .andExpect(jsonPath("$.page.number", is(1))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java index feeff4668b63..7d4d72116150 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java @@ -51,8 +51,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe @Autowired private ChoiceAuthorityService cas; - private final static int PAGE_TOTAL_ELEMENTS = 30; - private final static int PAGE_TOTAL_PAGES = 15; + private final static int PAGE_TOTAL_ELEMENTS = 32; + private final static int PAGE_TOTAL_PAGES = 16; @Test public void findAll() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionSectionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionSectionsControllerIT.java index 8ff9ff19217f..5fd5ba9fba50 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionSectionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionSectionsControllerIT.java @@ -68,8 +68,7 @@ public void testFindOne() throws Exception { getClient(token).perform(get("/api/config/submissionsections/collection")) .andExpect(status().isOk()) - .andExpect(jsonPath("$", matches("collection", true, "collection", - of("submission", "hidden", "workflow", "hidden", "edit", "hidden")))); + .andExpect(jsonPath("$", matches("collection", true, "collection"))); getClient(token).perform(get("/api/config/submissionsections/traditionalpageone")) .andExpect(status().isOk()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index ba5efb436b30..356f74bba9f1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -2708,4 +2708,35 @@ public void testAuthorFindOne() throws Exception { } + @Test + public void testWorkflowWithHiddenSections() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmissionDefinition("test-hidden") + .withWorkflowGroup(1, eperson) + .build(); + + WorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, collection) + .withTitle("Workflow Item") + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.test-outside-workflow-hidden").exists()) + .andExpect(jsonPath("$.sections.test-outside-submission-hidden").doesNotExist()) + .andExpect(jsonPath("$.sections.test-never-hidden").exists()) + .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); + + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index b8533e616040..6d40309f19be 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -9700,4 +9700,36 @@ public void testAuthorFindOneAndDeposit() throws Exception { } + @Test + public void testSubmissionWithHiddenSections() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmissionDefinition("test-hidden") + .build(); + + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .withIssueDate("2023-01-01") + .withType("Type") + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.test-outside-workflow-hidden").doesNotExist()) + .andExpect(jsonPath("$.sections.test-outside-submission-hidden").exists()) + .andExpect(jsonPath("$.sections.test-never-hidden").exists()) + .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); + + } + } diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 2dac2ab13e5e..b892739bab40 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -59,7 +59,6 @@ submit.progressbar.collection org.dspace.app.rest.submit.step.CollectionStep collection - submission submit.progressbar.upload From 84f9007939bed194e5e3d1781e8797e3fe3235cb Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 8 Mar 2023 11:56:34 +0100 Subject: [PATCH 320/394] [DSC-977] When a submission step is hidden the validation should not be applied --- .../service/impl/ValidationServiceImpl.java | 35 ++++++++++++++++--- .../app/rest/EditItemRestRepositoryIT.java | 28 +++++++++++++++ .../rest/WorkflowItemRestRepositoryIT.java | 28 +++++++++++++++ .../rest/WorkspaceItemRestRepositoryIT.java | 27 ++++++++++++++ 4 files changed, 113 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java b/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java index 10081bb914b5..86642b9fc20b 100644 --- a/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java @@ -9,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import javax.annotation.PostConstruct; import org.dspace.app.util.SubmissionConfig; @@ -54,23 +55,47 @@ private void setup() throws SubmissionConfigReaderException { @Override public List validate(Context context, InProgressSubmission obj) { + SubmissionConfig submissionConfig = submissionConfigReader.getSubmissionConfigByInProgressSubmission(obj); + List errors = new ArrayList(); - SubmissionConfig submissionConfig = submissionConfigReader.getSubmissionConfigByInProgressSubmission(obj); + errors.addAll(notHiddenStepsValidations(context, obj, submissionConfig)); + errors.addAll(globalValidations(context, obj, submissionConfig)); + + return errors; + + } + + private List notHiddenStepsValidations(Context context, InProgressSubmission obj, + SubmissionConfig submissionConfig) { + + List errors = new ArrayList(); for (SubmissionStepConfig stepConfig : submissionConfig) { + + if (stepConfig.isHiddenForInProgressSubmission(obj)) { + continue; + } + stepValidators.stream() .filter(validation -> validation.getName().equals(stepConfig.getType())) .flatMap(validation -> validation.validate(context, obj, stepConfig).stream()) .forEach(errors::add); - } - globalValidators.stream() - .flatMap(validator -> validator.validate(context, obj, submissionConfig).stream()) - .forEach(errors::add); + } return errors; } + private List globalValidations(Context context, InProgressSubmission obj, + SubmissionConfig submissionConfig) { + + return globalValidators.stream() + .flatMap(validator -> validator.validate(context, obj, submissionConfig).stream()) + .collect(Collectors.toList()); + + } + + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java index cfcc684d019c..2e403b24dfa8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java @@ -15,6 +15,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -1704,6 +1705,33 @@ public void testEditWithHiddenSections() throws Exception { } + @Test + public void testValidationWithHiddenSteps() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withEntityType("Publication") + .build(); + + Item item = ItemBuilder.createItem(context, collection) + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/core/edititems/" + item.getID() + ":MODE-TEST-HIDDEN")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors", hasSize(1))) + .andExpect(jsonPath("$.errors[0].message", is("error.validation.required"))) + .andExpect(jsonPath("$.errors[0].paths", contains("/sections/test-outside-workflow-hidden/dc.title"))); + } + private Bitstream getBitstream(Item item, String name) throws SQLException { return bitstreamService.getBitstreamByName(item, "ORIGINAL", name); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index 356f74bba9f1..2e4b602c180a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -2739,4 +2739,32 @@ public void testWorkflowWithHiddenSections() throws Exception { } + @Test + public void testValidationWithHiddenSteps() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmissionDefinition("test-hidden") + .withWorkflowGroup(1, eperson) + .build(); + + WorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, collection) + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/workflow/workflowitems/" + workflowItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors", hasSize(1))) + .andExpect(jsonPath("$.errors[0].message", is("error.validation.required"))) + .andExpect(jsonPath("$.errors[0].paths", contains("/sections/test-outside-workflow-hidden/dc.title"))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 5b0a3d520e84..e8bd35ff5218 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -9750,6 +9750,33 @@ public void testSubmissionWithHiddenSections() throws Exception { .andExpect(jsonPath("$.sections.test-always-hidden").doesNotExist()); } + @Test + public void testValidationWithHiddenSteps() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withSubmissionDefinition("test-hidden") + .build(); + + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .build(); + + context.restoreAuthSystemState(); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/submission/workspaceitems/" + workspaceItem.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors", hasSize(1))) + .andExpect(jsonPath("$.errors[0].message", is("error.validation.required"))) + .andExpect(jsonPath("$.errors[0].paths", contains("/sections/test-outside-submission-hidden/dc.type"))); + } + @Test public void lookupScopusMetadataWhenHaveExtractMetadataStepExceptionTest() throws Exception { context.turnOffAuthorisationSystem(); From 95131e301aedb34670d13003734a5961cbc68588 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 28 Mar 2023 12:00:49 +0200 Subject: [PATCH 321/394] [DSC-573] Skipped validations event for read only steps --- .../org/dspace/app/util/SubmissionStepConfig.java | 13 +++++++++++++ .../service/impl/ValidationServiceImpl.java | 6 +++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java index 7c364805c300..03d5dc6b7657 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java +++ b/dspace-api/src/main/java/org/dspace/app/util/SubmissionStepConfig.java @@ -201,6 +201,19 @@ public boolean isHiddenForInProgressSubmission(InProgressSubmission obj) { } + public boolean isReadOnlyForInProgressSubmission(InProgressSubmission obj) { + + String scopeToCheck = getScope(obj); + + if (scope == null || scopeToCheck == null) { + return false; + } + + String visibility = scope.equalsIgnoreCase(scopeToCheck) ? getVisibility() : getVisibilityOutside(); + return "read-only".equalsIgnoreCase(visibility); + + } + private String getScope(InProgressSubmission obj) { if (HibernateProxyHelper.getClassWithoutInitializingProxy(obj).equals(WorkspaceItem.class)) { return "submission"; diff --git a/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java b/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java index 86642b9fc20b..b4c9b4bc4c1a 100644 --- a/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/validation/service/impl/ValidationServiceImpl.java @@ -73,7 +73,7 @@ private List notHiddenStepsValidations(Context context, InProgr for (SubmissionStepConfig stepConfig : submissionConfig) { - if (stepConfig.isHiddenForInProgressSubmission(obj)) { + if (isStepHiddenOrReadOnly(stepConfig, obj)) { continue; } @@ -97,5 +97,9 @@ private List globalValidations(Context context, InProgressSubmi } + private boolean isStepHiddenOrReadOnly(SubmissionStepConfig stepConfig, InProgressSubmission obj) { + return stepConfig.isHiddenForInProgressSubmission(obj) || stepConfig.isReadOnlyForInProgressSubmission(obj); + } + } From fd67aa6555aa1af8f9bc2d1b40fa5bf99744d81e Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 28 Mar 2023 17:52:48 +0200 Subject: [PATCH 322/394] [DSC-573] Fixed SubmissionDefinitionsControllerIT test --- .../app/rest/SubmissionDefinitionsControllerIT.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index f5313621c67f..89689959114d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -322,10 +322,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=14"), Matchers.containsString("size=1")))) + Matchers.containsString("page=15"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(15))) - .andExpect(jsonPath("$.page.totalPages", is(15))) + .andExpect(jsonPath("$.page.totalElements", is(16))) + .andExpect(jsonPath("$.page.totalPages", is(16))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -348,10 +348,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=14"), Matchers.containsString("size=1")))) + Matchers.containsString("page=15"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(15))) - .andExpect(jsonPath("$.page.totalPages", is(15))) + .andExpect(jsonPath("$.page.totalElements", is(16))) + .andExpect(jsonPath("$.page.totalPages", is(16))) .andExpect(jsonPath("$.page.number", is(1))); } From 7581a953de2fd22b74c0bdea6afde49811259129 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 30 Mar 2023 10:44:16 +0200 Subject: [PATCH 323/394] [DSC-573] Fixed CorrectionStepIT test --- .../src/test/java/org/dspace/app/rest/CorrectionStepIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java index 06f85d5aa0b5..8af1c02096d5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java @@ -233,7 +233,7 @@ public void checkCorrection() throws Exception { //add an asbtract description Map addValue = new HashMap(); - addValue.put("value","Description Test"); + addValue.put("value", "New Description"); addGrant = new ArrayList(); addGrant.add(new AddOperation("/sections/traditionalpagetwo/dc.description.abstract", List.of(addValue))); patchBody = getPatchContent(addGrant); @@ -266,7 +266,7 @@ public void checkCorrection() throws Exception { .andExpect(jsonPath("$.sections.correction.empty", is(false))) .andExpect(jsonPath("$.sections.correction.metadata", containsInAnyOrder(matchMetadataCorrection("New Title"), - matchMetadataCorrection("Description Test"), + matchMetadataCorrection("New Description"), matchMetadataCorrection("ExtraEntry")))); } From 5e12da0a702802adf9df38ca933fd701501c5bac Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Thu, 30 Mar 2023 17:22:52 +0200 Subject: [PATCH 324/394] [DSC-573] Fixed CorrectionStepIT --- .../org/dspace/app/rest/CorrectionStepIT.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java index 8af1c02096d5..b7ea2f1ba6b0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java @@ -69,7 +69,7 @@ /** * Integration tests for {@link CorrectionStep}. * - * @author Giuseppe Digilio (luca.giamminonni at 4science.it) + * @author Giuseppe Digilio (giuseppe.digilio at 4science.it) * */ public class CorrectionStepIT extends AbstractControllerIntegrationTest { @@ -131,7 +131,7 @@ public void setup() throws Exception { date = "2020-02-20"; subject = "ExtraEntry"; - title = "Title " + (new Date().getTime()); + title = "Title " + new Date().getTime(); type = "text"; itemToBeCorrected = ItemBuilder.createItem(context, collection) @@ -153,6 +153,7 @@ public void setup() throws Exception { context.restoreAuthSystemState(); } + @Override @After public void destroy() throws Exception { //Clean up the database for the next test @@ -205,13 +206,14 @@ public void checkCorrection() throws Exception { .andDo(result -> workspaceItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); List relationshipList = relationshipService.findByItem(context, itemToBeCorrected); - assert (relationshipList.size() > 0); + assert relationshipList.size() > 0; Item correctedItem = relationshipList.get(0).getLeftItem(); WorkspaceItem newWorkspaceItem = workspaceItemService.findByItem(context,correctedItem); //make a change on the title Map value = new HashMap(); - value.put("value", "New Title"); + final String newTitle = "New Title"; + value.put("value", newTitle); List addGrant = new ArrayList(); addGrant.add(new ReplaceOperation("/sections/traditionalpageone/dc.title/0", value)); String patchBody = getPatchContent(addGrant); @@ -233,7 +235,8 @@ public void checkCorrection() throws Exception { //add an asbtract description Map addValue = new HashMap(); - addValue.put("value", "New Description"); + final String newDescription = "New Description"; + addValue.put("value", newDescription); addGrant = new ArrayList(); addGrant.add(new AddOperation("/sections/traditionalpagetwo/dc.description.abstract", List.of(addValue))); patchBody = getPatchContent(addGrant); @@ -258,16 +261,22 @@ public void checkCorrection() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); //check if the correction is present + final String extraEntry = "ExtraEntry"; getClient(tokenAdmin).perform(get("/api/workflow/workflowitems/" + workflowItemIdRef.get())) //The status has to be 200 OK .andExpect(status().isOk()) //The array of browse index should have a size equals to 4 .andExpect(jsonPath("$.sections.correction.metadata", hasSize(equalTo(3)))) .andExpect(jsonPath("$.sections.correction.empty", is(false))) - .andExpect(jsonPath("$.sections.correction.metadata", - containsInAnyOrder(matchMetadataCorrection("New Title"), - matchMetadataCorrection("New Description"), - matchMetadataCorrection("ExtraEntry")))); + .andExpect( + jsonPath("$.sections.correction.metadata", + containsInAnyOrder( + matchMetadataCorrection(newTitle), + matchMetadataCorrection(newDescription), + matchMetadataCorrection(extraEntry) + ) + ) + ); } @@ -285,7 +294,7 @@ public void checkEmptyCorrection() throws Exception { .andDo(result -> workspaceItemIdRef.set(read(result.getResponse().getContentAsString(), "$.id"))); List relationshipList = relationshipService.findByItem(context, itemToBeCorrected); - assert (relationshipList.size() > 0); + assert relationshipList.size() > 0; Item correctedItem = relationshipList.get(0).getLeftItem(); WorkspaceItem newWorkspaceItem = workspaceItemService.findByItem(context, correctedItem); @@ -311,8 +320,8 @@ public void checkEmptyCorrection() throws Exception { private static Matcher matchMetadataCorrection(String value) { return Matchers.anyOf( - hasJsonPath("$.newValues[0]", is(value)), - hasJsonPath("$.oldValues[0]", is(value))); + hasJsonPath("$.newValues[0]", equalTo(value)), + hasJsonPath("$.oldValues[0]", equalTo(value))); } private void deletePoolTask(PoolTask poolTask) { From 359765d111d614d27bf3102f053945c1104052bd Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Thu, 30 Mar 2023 18:03:16 +0200 Subject: [PATCH 325/394] [DSC-573] Fixed CorectionStepIT fix: - replaced `anyOf` with multiple `hasItem` checks --- .../java/org/dspace/app/rest/CorrectionStepIT.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java index b7ea2f1ba6b0..34665592823e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CorrectionStepIT.java @@ -9,9 +9,9 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -268,15 +268,9 @@ public void checkCorrection() throws Exception { //The array of browse index should have a size equals to 4 .andExpect(jsonPath("$.sections.correction.metadata", hasSize(equalTo(3)))) .andExpect(jsonPath("$.sections.correction.empty", is(false))) - .andExpect( - jsonPath("$.sections.correction.metadata", - containsInAnyOrder( - matchMetadataCorrection(newTitle), - matchMetadataCorrection(newDescription), - matchMetadataCorrection(extraEntry) - ) - ) - ); + .andExpect(jsonPath("$.sections.correction.metadata",hasItem(matchMetadataCorrection(newTitle)))) + .andExpect(jsonPath("$.sections.correction.metadata",hasItem(matchMetadataCorrection(newDescription)))) + .andExpect(jsonPath("$.sections.correction.metadata",hasItem(matchMetadataCorrection(extraEntry)))); } From 3d05e8e2c78ae36e93cd591d92125d591f449deb Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 18 Apr 2023 01:05:51 -0300 Subject: [PATCH 326/394] Fixed typo and duplicated sentences --- README.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 39ab43db2778..618a01456180 100644 --- a/README.md +++ b/README.md @@ -25,24 +25,17 @@ Past releases and future releases are documented in the [RoadMap page](https://w Documentation is available on our [Documentation Wiki](https://wiki.lyrasis.org/display/DSPACECRIS/Technical+and+User+documentation) please check also the documentation from the parent DSpace project as basic features and principle are common and only described in the [DSpace documentation](https://wiki.lyrasis.org/display/DSDOC/). -The latest DSpace Installation instructions are available at: -https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace +The latest DSpace Installation instructions are available [here](https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace). Some extra step to initialize the DSpace-CRIS with proper default can be found in our documentation (see above). -some extra step to initialize te DSpace-CRIS with proper default can be found in our documentation (see above) +Please be aware that, as a Java web application, DSpace-CRIS requires a database (PostgreSQL or Oracle), a servlet container (usually Tomcat) and a SOLR instance in order to function. -Please be aware that, as a Java web application, DSpace-CRIS requires a database (PostgreSQL) -and a servlet container (usually Tomcat) in order to function. More information about these and all other prerequisites can be found in the Installation instructions above. ## Running DSpace-CRIS 7 in Docker -NOTE: At this time, we do not have production-ready Docker images for DSpace-CRIS. +**NOTE**: At this time, we do not have production-ready Docker images for DSpace-CRIS. That said, we do have quick-start Docker Compose scripts for development or testing purposes. -Please be aware that, as a Java web application, DSpace-CRIS requires a database (PostgreSQL or Oracle), a servlet container (usually Tomcat) and a SOLR instance in order to function. - -More information about these and all other prerequisites can be found in the Installation instructions above. - ## Getting Help DSpace-CRIS has a [dedicated slack channel](https://dspace-org.slack.com/messages/dspace-cris/) in the DSpace.org workspace. From 786a8910a8911772bacd93ec15e0e5549960aaa6 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Fri, 28 Apr 2023 10:15:43 +0200 Subject: [PATCH 327/394] insert relation right and leftward type into relationship table --- dspace/etc/migration/relations_migrations.ktr | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dspace/etc/migration/relations_migrations.ktr b/dspace/etc/migration/relations_migrations.ktr index e2bc269baecc..5b9987492b1b 100644 --- a/dspace/etc/migration/relations_migrations.ktr +++ b/dspace/etc/migration/relations_migrations.ktr @@ -1004,6 +1004,14 @@ WHERE label = ? left_place left_place + + leftward_value + leftward_type + + + rightward_value + rightward_type + From 01377d93a6931175879415e925212523853a8417 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Fri, 28 Apr 2023 10:41:59 +0200 Subject: [PATCH 328/394] consider owner profile check in all languages align the language check to the pattern used in the regarding services and allow these functions for owners with metadatalanguage --- .../org/dspace/discovery/SharedWorkspaceSolrIndexPlugin.java | 2 +- .../OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SharedWorkspaceSolrIndexPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SharedWorkspaceSolrIndexPlugin.java index a30edbdf3f16..1e9fd3aa1b4b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SharedWorkspaceSolrIndexPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SharedWorkspaceSolrIndexPlugin.java @@ -122,7 +122,7 @@ private void addRead(SolrInputDocument document, Optional subm) { private Optional findOwner(Context context, Item source) throws SQLException { List metadata = - itemService.getMetadata(source, "dspace", "object", "owner", null); + itemService.getMetadata(source, "dspace", "object", "owner", Item.ANY); if (metadata.isEmpty()) { return Optional.empty(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java index 9f6d83695295..acabb5b14fa1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java @@ -17,6 +17,7 @@ import org.dspace.app.rest.utils.ContextUtil; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; +import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.orcid.OrcidQueue; @@ -89,7 +90,7 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t private boolean hasAccess(Context context, EPerson currentUser, OrcidQueue orcidQueue) { if (orcidQueue != null) { List value = itemService.getMetadata(orcidQueue.getProfileItem(), - "dspace", "object", "owner", null); + "dspace", "object", "owner", Item.ANY); if (value.get(0).getAuthority().equals(currentUser.getID().toString())) { return true; } From c0f8f58e8630114097fbfbc7878fe7fc67f998d0 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 3 May 2023 15:18:03 +0200 Subject: [PATCH 329/394] fixed schema errors in submission-forms.xml --- dspace/config/submission-forms.xml | 947 ++++++++++++++--------------- 1 file changed, 473 insertions(+), 474 deletions(-) diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 8bb80709bb68..a31b83eac506 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -8,33 +8,33 @@ dc title + false onebox - false - You must enter a title for the file. + You must enter a title for the file. dc description + false textarea - false - + dc type + false dropdown - false - Personal picture, logo, main article, etc. + @@ -44,11 +44,11 @@ dc contributor author + false onebox - false - You must enter at least the author. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the author. @@ -56,11 +56,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -70,11 +70,11 @@ dc contributor editor + false onebox - false - You must enter at least the author. The editors of this publication. + You must enter at least the author. @@ -82,11 +82,11 @@ oairecerif editor affiliation + false onebox - false - Enter the affiliation of the editor as stated on the publication. + @@ -96,11 +96,11 @@ dc relation funding + false onebox - false - You must enter at least the funding name. Enter the name of funding, if any, that has supported this publication + You must enter at least the funding name. @@ -108,11 +108,11 @@ dc relation grantno + false onebox - false - If the funding is not found in the system please enter the funding identifier / grant no + @@ -122,11 +122,11 @@ dc relation funding + false onebox - false - You must enter at least the funding name. Enter the name of funding, if any, that has supported this product + You must enter at least the funding name. @@ -134,11 +134,11 @@ dc relation grantno + false onebox - false - If the funding is not found in the system please enter the funding identifier / grant no + @@ -148,11 +148,11 @@ dc relation funding + false onebox - false - You must enter at least the funding name. Enter the name of funding, if any, that has supported this patent + You must enter at least the funding name. @@ -160,11 +160,11 @@ dc relation grantno + false onebox - false - If the funding is not found in the system please enter the funding identifier / grant no + @@ -174,45 +174,45 @@ oairecerif affiliation role - - onebox false - + + onebox + oairecerif person affiliation - - onebox false - You must enter at least the organisation of your affiliation. + + onebox + You must enter at least the organisation of your affiliation. oairecerif affiliation startDate - - date false - + + date + oairecerif affiliation endDate - - date false - + + date + @@ -221,45 +221,45 @@ crisrp qualification - - onebox false - You must enter the organisation + + onebox + You must enter the organisation crisrp qualification role - - onebox false - You must enter the qualification title. + + onebox + You must enter the qualification title. crisrp qualification start - - date false - + + date + crisrp qualification end - - date false - + + date + @@ -269,44 +269,44 @@ crisrp education role - - onebox false - You must enter the degree/title + + onebox + You must enter the degree/title crisrp education - - onebox false - You must enter the organisation + + onebox + You must enter the organisation crisrp education start - - date false - + + date + crisrp education end - - date false - + + date + @@ -316,11 +316,11 @@ dc contributor author + false name - false - You must enter at least the author. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the author. @@ -328,11 +328,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -342,11 +342,11 @@ dc contributor author + false name - false - You must enter at least the inventor. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the inventor. @@ -354,11 +354,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -368,11 +368,11 @@ oairecerif identifier url + false onebox - false - You must enter at least the site url. + You must enter at least the site url. @@ -380,11 +380,11 @@ crisrp site title + false onebox - false - + @@ -394,55 +394,55 @@ dc identifier issn + false onebox - false - + dc title + false onebox - false - You must enter a main title for this item. + You must enter a main title for this item. dc publisher + false onebox - false - + dc subject + true tag - true - + dc description + false textarea - false - + @@ -451,23 +451,23 @@ dc identifier + true qualdrop_value - true - If the item has any identification numbers or codes associated with it, please enter the types and the actual numbers or codes. + dc title + false onebox - false - You must enter a main title for this item. Enter the main title of the item. + You must enter a main title for this item. @@ -475,11 +475,11 @@ dc title alternative + true onebox - true - If the item has any alternative titles, please enter them here. + @@ -487,13 +487,13 @@ dc date issued + false date - false - You must enter at least the year. Please give the date of previous publication or public distribution. You can leave out the day and/or month if they aren't applicable. + You must enter at least the year. @@ -501,11 +501,11 @@ dc contributor author + true group - true - Enter the names of the authors of this item. + @@ -513,22 +513,22 @@ dc contributor editor + true group - true - The editors of this publication. + dc type + false onebox - false - You must select a publication type Select the type(s) of content of the item. + You must select a publication type publication-coar-types @@ -538,23 +538,23 @@ dc identifier + true qualdrop_value - true - If the item has any identification numbers or codes associated with it, please enter the types and the actual numbers or codes. + dc title + false onebox - false - You must enter a main title for this item. Enter the main title of the item. + You must enter a main title for this item. @@ -562,11 +562,11 @@ dc title alternative + true onebox - true - If the item has any alternative titles, please enter them here. + @@ -574,13 +574,13 @@ dc date issued + false date - false - You must enter at least the year. Please give the date of previous publication or public distribution. You can leave out the day and/or month if they aren't applicable. + You must enter at least the year. @@ -588,11 +588,11 @@ dc contributor author + true group - true - Enter the names of the authors of this item. + @@ -600,22 +600,22 @@ dc contributor group + true onebox - true - The editors of this publication. + dc type + false onebox - false - You must select a publication type Select the type(s) of content of the item. + You must select a publication type publication-coar-types @@ -626,11 +626,11 @@ dc contributor author + false onebox - false - You must enter at least the author. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the author. @@ -638,11 +638,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -652,11 +652,11 @@ dc contributor editor + false onebox - false - You must enter at least the author. The editors of this publication. + You must enter at least the author. @@ -664,11 +664,11 @@ oairecerif editor affiliation + false onebox - false - Enter the affiliation of the editor as stated on the publication. + @@ -677,23 +677,23 @@ dc identifier + true qualdrop_value - true - If the item has any identification numbers or codes associated with it, please enter the types and the actual numbers or codes. + dc title + false onebox - false - You must enter a main title for this item. Enter the main title of the item. + You must enter a main title for this item. @@ -701,11 +701,11 @@ dc title alternative + true onebox - true - If the item has any alternative titles, please enter them here. + @@ -713,13 +713,13 @@ dc date issued + false date - false - You must enter at least the year. Please give the date of previous publication or public distribution. You can leave out the day and/or month if they aren't applicable. + You must enter at least the year. @@ -727,11 +727,11 @@ dc contributor author + true group - true - Enter the names of the authors of this item. + @@ -739,22 +739,22 @@ dc contributor group + true onebox - true - The editors of this publication. + dc type + false onebox - false - You must select a publication type Select the type(s) of content of the item. + You must select a publication type publication-coar-types @@ -765,11 +765,11 @@ dc contributor author + false onebox - false - You must enter at least the author. Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J]. + You must enter at least the author. @@ -777,11 +777,11 @@ oairecerif author affiliation + false onebox - false - Enter the affiliation of the author as stated on the publication. + @@ -791,11 +791,11 @@ dc contributor editor + false onebox - false - You must enter at least the author. The editors of this publication. + You must enter at least the author. @@ -803,37 +803,37 @@ oairecerif editor affiliation + false onebox - false - Enter the affiliation of the editor as stated on the publication. + - +
dc language iso + false dropdown - false - Select the language of the main content of the item. If the language does not appear in the list, please select 'Other'. If the content does not really have a language (for example, if it is a dataset or an image) please select 'N/A'. + dc subject + true tag - true - Enter appropriate subject keywords or phrases. + @@ -841,11 +841,11 @@ datacite subject fos + true onebox - true - + oecd @@ -854,11 +854,11 @@ dc description abstract + false textarea - false - Enter the abstract of the item. + @@ -868,12 +868,12 @@ dc relation publication + false + publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 onebox - false - The publication where this publication is included. E.g. a book chapter lists here the book, a contribution to a conference lists here the conference proceeding. - publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 + @@ -881,12 +881,12 @@ dc relation isbn + false + publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 onebox - false - The ISBN of the book/report if it was not found in the system - publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 + @@ -894,12 +894,12 @@ dc relation doi + false + publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 onebox - false - The DOI of the book/report if it was not found in the system - publication-coar-types:c_3248,publication-coar-types:c_5794,publication-coar-types:c_6670 + @@ -907,11 +907,11 @@ dc relation journal + false onebox - false - The journal or Serie where this publication has been published + @@ -919,11 +919,11 @@ dc relation ispartofseries + true series - true - Enter the series and number assigned to this item by your community. + @@ -931,11 +931,11 @@ dc relation issn + false onebox - false - The journal or Serie ISSN if it was not found in the system + @@ -943,12 +943,12 @@ dc coverage publication + false + publication-coar-types:c_efa0,publication-coar-types:c_ba08 onebox - false - The publication object of the review - publication-coar-types:c_efa0,publication-coar-types:c_ba08 + @@ -956,12 +956,12 @@ dc coverage isbn + false + publication-coar-types:c_efa0,publication-coar-types:c_ba08 onebox - false - The ISBN of the reviewed item if it was not found in the system - publication-coar-types:c_efa0,publication-coar-types:c_ba08 + @@ -969,12 +969,12 @@ dc coverage doi + false + publication-coar-types:c_efa0,publication-coar-types:c_ba08 onebox - false - The DOI of the reviewed item if it was not found in the system - publication-coar-types:c_efa0,publication-coar-types:c_ba08 + @@ -982,11 +982,11 @@ dc description sponsorship + true onebox - true - Enter the name of any sponsors. + @@ -994,11 +994,11 @@ oaire citation volume + false onebox - false - If applicable, the volume of the publishing channel where this publication appeared + @@ -1006,11 +1006,11 @@ oaire citation issue + false onebox - false - If applicable, the issue of the publishing channel where this publication appeared + @@ -1018,11 +1018,11 @@ oaire citation startPage + false onebox - false - If applicable, the page where this publication starts + @@ -1030,11 +1030,11 @@ oaire citation endPage + false onebox - false - If applicable, the page where this publication ends + @@ -1044,11 +1044,11 @@ dc relation funding + true group - true - Acknowledge the funding received for this publication. + @@ -1056,11 +1056,11 @@ dc relation project + true onebox - true - Enter the name of project, if any, that has produced this publication. It is NOT necessary to list the projects connected with an acknowledge funding. + @@ -1068,11 +1068,11 @@ dc relation conference + true onebox - true - Enter the name of the conference where the item has been presented, if any. + @@ -1080,11 +1080,11 @@ dc relation product + true onebox - true - Link the item to one or more existent dataset in the repository used or described by the publication or, put here the dataset citation + @@ -1092,22 +1092,22 @@ dc identifier citation + false onebox - false - Enter the standard citation for the previously issued instance of this item. + dc description + false textarea - false - Enter any other description or comments in this box. + @@ -1115,11 +1115,11 @@ dc description sponsorship + true onebox - true - Enter the name of any sponsors. + @@ -1128,23 +1128,23 @@ dc identifier + true qualdrop_value - true - If the item has any identification numbers or codes associated with it, please enter the types and the actual numbers or codes. + dc title + false onebox - false - You must enter a main title for this item. Enter the main title of the item. + You must enter a main title for this item. @@ -1152,11 +1152,11 @@ dc title alternative + true onebox - true - If the item has any alternative titles, please enter them here. + @@ -1164,11 +1164,11 @@ dc date issued + false date - false - You must enter at least the year. Please give the date of previous publication or public distribution. You can leave out the day and/or month if they aren't applicable. + You must enter at least the year. @@ -1176,11 +1176,11 @@ dc description version + false onebox - false - If applicable, the version of the product + @@ -1188,22 +1188,22 @@ dc contributor author + true group - true - Enter the names of the authors of this item. + dc type + false onebox - false - Nothing to do here. Note for administrators, this metadata could be completely hide using template item + product-coar-types @@ -1214,22 +1214,22 @@ dc language iso + false dropdown - false - Select, if applicable, the language of the main content of the item. If the language does not appear in the list, please select 'Other'. If the content does not really have a language (for example, if it is a dataset or an image) please select 'N/A'. + dc subject + true tag - true - Enter appropriate subject keywords or phrases. + @@ -1237,11 +1237,11 @@ datacite subject fos + true onebox - true - + oecd @@ -1250,11 +1250,11 @@ dc description abstract + false textarea - false - Enter the abstract of the item. + @@ -1263,11 +1263,11 @@ dc publisher + true onebox - true - The publisher or publishers of this product + @@ -1275,11 +1275,11 @@ dc relation ispartofseries + true series - true - Link to the research output of which this product is a part (e.g. a data set collection that contains it). + @@ -1287,11 +1287,11 @@ dc relation issn + false onebox - false - The journal or Serie ISSN if it was not found in the system + @@ -1299,11 +1299,11 @@ dc relation funding + true group - true - Acknowledge the funding received for this product. + @@ -1311,11 +1311,11 @@ dc relation project + true onebox - true - Enter the name of project, if any, that has produced this product. It is NOT necessary to list the projects connected with an acknowledge funding. + @@ -1323,11 +1323,11 @@ dc relation conference + false onebox - false - The event where this product was presented or that is recorded in the product. + @@ -1335,11 +1335,11 @@ dc relation equipment + true onebox - true - The equipment that generated this product + @@ -1347,11 +1347,11 @@ dc relation references + true onebox - true - Result outputs that are referenced by this product + @@ -1359,11 +1359,11 @@ dc relation publication + true onebox - true - Result outputs that use this product + @@ -1373,11 +1373,11 @@ dc identifier patentno + false onebox - false - The patent number + @@ -1385,42 +1385,42 @@ dc identifier applicationnumber + false onebox - false - The application number + dcterms dateSubmitted + false date - false - Date on which the application was physically received at the Patent Authority. Also named Filling Date + dc title + false onebox - false - You must specify a title for the patent The title of the patent + You must specify a title for the patent dcterms dateAccepted + false date - false - Date on which the application has been granted by the Patent Office. + @@ -1428,11 +1428,11 @@ dc date issued + false date - false - Date of making available to the public by printing or similar process of a patent document on which grant has taken place on or before the said date + @@ -1440,44 +1440,44 @@ dc contributor author + true group - true - The inventor: The actual devisor of an invention that is the subject of a patent. + dcterms rightsHolder + true onebox - true - The holders of this patent + dc publisher + true onebox - true - The issuer of the patent: the patent office + dc type + false onebox - false - You must select a patent type Select the type of content of the patent. + You must select a patent type patent-coar-types @@ -1488,22 +1488,22 @@ dc language iso + false dropdown - false - Select the country and its language. + dc subject + true onebox - true - Enter appropriate subject keywords or phrases. + @@ -1511,11 +1511,11 @@ dc description abstract + false textarea - false - Enter the description of the patent. + @@ -1525,11 +1525,11 @@ dc relation funding + true group - true - Acknowledge the funding received for this patent. + @@ -1537,11 +1537,11 @@ dc relation project + true onebox - true - Enter the name of project, if any, that has produced this patent. It is NOT necessary to list the projects connected with an acknowledge funding. + @@ -1549,11 +1549,11 @@ dc relation patent + true onebox - true - Patents that precede (i.e., have priority over) this patent + @@ -1561,11 +1561,11 @@ dc relation references + true onebox - true - Result outputs that are referenced by this patent + @@ -1574,22 +1574,22 @@ dc title + false name - false - You must enter least at the Surname. + You must enter least at the Surname. crisrp name + false name - false - + @@ -1597,11 +1597,11 @@ crisrp name translated + false name - false - + @@ -1609,84 +1609,84 @@ crisrp name variant + true name - true - + person givenName + false onebox - false - + person familyName + false onebox - false - + person birthDate + false date - false - + oairecerif person gender + false dropdown - false - + person jobTitle + false onebox - false - + person affiliation name + false onebox - false - + crisrp workgroup + true onebox - true - + @@ -1694,33 +1694,33 @@ oairecerif identifier url + true group - true - + person email + false onebox - false - + dc subject + true tag - true - + @@ -1728,11 +1728,11 @@ datacite subject fos + true onebox - true - + oecd @@ -1741,11 +1741,11 @@ person identifier orcid + false onebox - false - Settable by connecting the entity with ORCID + all @@ -1754,11 +1754,11 @@ person identifier scopus-author-id + true onebox - true - + @@ -1766,11 +1766,11 @@ person identifier rid + true onebox - true - + @@ -1778,11 +1778,11 @@ oairecerif person affiliation + true inline-group - true - + @@ -1790,55 +1790,55 @@ dc description abstract + false textarea - false - + crisrp education + true inline-group - true - + crisrp country + false dropdown - false - + crisrp qualification + true inline-group - true - + person knowsLanguage + true dropdown - true - + @@ -1846,11 +1846,11 @@ cris policy eperson + false onebox - false - + @@ -1858,11 +1858,11 @@ cris policy group + false onebox - false - + @@ -1871,77 +1871,77 @@ dc title + false onebox - false - You must enter the oganization name. + You must enter the oganization name. oairecerif acronym + false onebox - false - + organization parentOrganization + false onebox - false - + crisou director + false onebox - false - + organization foundingDate + false date - false - + crisou boards + true onebox - true - + organization identifier + true qualdrop_value - true - + @@ -1949,11 +1949,11 @@ oairecerif identifier url + true onebox - true - + @@ -1961,11 +1961,11 @@ dc description abstract + false textarea - false - + @@ -1973,32 +1973,32 @@ organization address addressLocality + false onebox - false - + organization address addressCountry + false dropdown - false - + dc type + false dropdown - false - You must specify the organisation type + You must specify the organisation type @@ -2007,88 +2007,88 @@ dc title + false onebox - false - You must enter the project name. + You must enter the project name. oairecerif acronym + false onebox - false - + crispj coordinator + true onebox - true - + oairecerif internalid + false onebox - false - + crispj partnerou + true onebox - true - + crispj investigator + false onebox - false - You must enter the project coordinator. + You must enter the project coordinator. crispj openaireid + false onebox - false - + crispj organization + true onebox - true - + @@ -2096,32 +2096,32 @@ oairecerif identifier url + true onebox - true - + oairecerif oamandate + false dropdown - false - + oairecerif oamandate url + false onebox - false - + @@ -2129,21 +2129,21 @@ oairecerif project startDate + false date - false - + oairecerif project endDate + false date - false - + @@ -2151,22 +2151,22 @@ oairecerif project status + false onebox - false - + dc type + false dropdown - false - + @@ -2174,33 +2174,33 @@ dc description abstract + false textarea - false - + crispj coinvestigators + true onebox - true - + dc subject + true tag - true - + @@ -2208,11 +2208,11 @@ datacite subject fos + true onebox - true - + oecd @@ -2221,11 +2221,11 @@ dc relation equipment + true onebox - true - + @@ -2234,31 +2234,31 @@ dc title + false onebox - false - You must enter the equipment name. + You must enter the equipment name. oairecerif acronym + false onebox - false - + oairecerif internalid + false onebox - false - + @@ -2266,31 +2266,31 @@ dc relation project + false onebox - false - + oairecerif funder + false onebox - false - + oairecerif fundingParent + false onebox - false - Link this funding with its upper level if applicable + @@ -2298,53 +2298,53 @@ crisfund award url + false onebox - false - The url preferably on the funder website of the award notice + The url preferably on the funder website of the award notice oairecerif oamandate + false dropdown - false - + oairecerif oamandate url + false onebox - false - + oairecerif amount + false onebox - false - + oairecerif amount currency + false dropdown - false - + @@ -2352,97 +2352,97 @@ oairecerif funding identifier + false onebox - false - + oairecerif funding startDate + false date - false - + oairecerif funding endDate + false date - false - + dc type + false dropdown - false - + dc description + false textarea - false - + crisfund investigators + true onebox - true - + crisfund coinvestigators + true onebox - true - + crisfund leadorganizations + true onebox - true - + crisfund leadcoorganizations + true onebox - true - + @@ -2451,66 +2451,66 @@ dc title + false onebox - false - You must enter the equipment name. + You must enter the equipment name. oairecerif acronym + false onebox - false - + oairecerif internalid + false onebox - false - + crisequipment ownerou + false onebox - false - + crisequipment ownerrp + false onebox - false - + dc description + false textarea - false - + @@ -2519,33 +2519,33 @@ dc title + false onebox - false - + oairecerif acronym + false onebox - false - + dc type + false dropdown - false - + @@ -2553,21 +2553,21 @@ oairecerif event startDate + false date - false - + oairecerif event endDate + false date - false - + @@ -2575,11 +2575,11 @@ oairecerif event place + false onebox - false - + @@ -2587,77 +2587,77 @@ oairecerif event country + false dropdown - false - + crisevent organizerou + true onebox - true - + crisevent organizerpj + true onebox - true - + crisevent sponsorou + true onebox - true - + crisevent sponsorpj + true onebox - true - + crisevent partnerou + true onebox - true - + crisevent partnerpj + true onebox - true - + @@ -2665,22 +2665,22 @@ dc description abstract + false textarea - false - + dc subject + true tag - true - + @@ -2689,11 +2689,11 @@ cris owner + false onebox - false - + @@ -4397,4 +4397,3 @@ - From 4e2ebdffc8c1a92d8d210701bcfe840eae79da92 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Mon, 8 May 2023 16:37:28 +0200 Subject: [PATCH 330/394] reorder import to fix checkstyle unit test --- .../OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java index acabb5b14fa1..0e7dc6192182 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java @@ -16,8 +16,8 @@ import org.dspace.app.rest.model.OrcidQueueRest; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.content.MetadataValue; -import org.dspace.content.service.ItemService; import org.dspace.content.Item; +import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.orcid.OrcidQueue; From 47ae6ca17b5668cc8ecaa5dfa34e6c4a5a12da85 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Tue, 9 May 2023 09:17:31 +0200 Subject: [PATCH 331/394] yet another reorder import to fix checkstyle unit test --- .../OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java index 0e7dc6192182..7e7940372f7a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/OrcidHistorySendToOrcidRestPermissionEvaluatorPlugin.java @@ -15,8 +15,8 @@ import org.dspace.app.rest.model.OrcidQueueRest; import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.content.MetadataValue; import org.dspace.content.Item; +import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; From 933deba631a5d68eec6ca2411717d4d154711799 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 9 May 2023 15:02:39 +0200 Subject: [PATCH 332/394] [DSC-1030] Fixed context startup --- ...e_subscriptionparamter_change_columns_subscription_table.sql | 2 -- ...e_subscriptionparamter_change_columns_subscription_table.sql | 2 -- dspace/config/spring/api/core-services.xml | 1 - 3 files changed, 5 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql index dc187d3c2784..703a9c605f99 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -34,8 +34,6 @@ ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255); ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey; ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- -- -UPDATE subscription set dspace_object_id = collection_id , type = 'content'; --- ALTER TABLE subscription DROP CONSTRAINT IF EXISTS Subscription_collection_id_fk; -- ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id; diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql index 61e01494fcb3..2d632b58004d 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.5_2022.12.01__add_table_subscriptionparamter_change_columns_subscription_table.sql @@ -32,8 +32,6 @@ ALTER TABLE subscription ADD COLUMN if NOT EXISTS type CHARACTER VARYING(255); ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_dspaceobject_fkey; ALTER TABLE subscription ADD CONSTRAINT subscription_dspaceobject_fkey FOREIGN KEY (dspace_object_id) REFERENCES dspaceobject (uuid); -- -UPDATE subscription SET dspace_object_id = collection_id , type = 'content'; --- ALTER TABLE subscription DROP CONSTRAINT IF EXISTS subscription_collection_id_fkey; -- -- ALTER TABLE subscription DROP COLUMN IF EXISTS collection_id; diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index bb8c1cdf8e32..9003d3a65a26 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -109,7 +109,6 @@ - From ccc0f0e07ddc01fca046446419a1702f4015dbbe Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 10 May 2023 15:15:54 +0200 Subject: [PATCH 333/394] [DSC-1030] Fixed ChecksumHistoryDAOImplTest test --- .../org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java b/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java index 860df5e887a5..d41537533b2f 100644 --- a/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/checker/dao/impl/ChecksumHistoryDAOImplTest.java @@ -83,6 +83,7 @@ public void testDeleteByDateAndCode() context.turnOffAuthorisationSystem(); bss.update(context, bs); context.restoreAuthSystemState(); + context.flush(); cal.add(Calendar.DATE, -1); Date matchDate = cal.getTime(); From 73468f2d65e462e854221aafcd042b9761e35ecc Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 15 May 2023 11:32:27 +0200 Subject: [PATCH 334/394] [DSC-1030] Fixed OAIHarvesterIT tests --- .../src/main/java/org/dspace/content/CollectionServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index e98d08aacd86..367c7a5d34b1 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -963,7 +963,7 @@ public List findCollectionsWithSubmit(String q, Context context, Com discoverQuery.setStart(offset); discoverQuery.setMaxResults(limit); discoverQuery.setSortField(SOLR_SORT_FIELD, SORT_ORDER.asc); - DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, null, community, q); + DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery, entityType, community, q); for (IndexableObject solrCollections : resp.getIndexableObjects()) { Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); collections.add(c); From d0ee3eb8cb8339c18ec2708321db235e43ba2454 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 15 May 2023 12:21:10 +0200 Subject: [PATCH 335/394] [DSC-1030] Removed S3BitStoreServiceTest --- ...tionTest.java => S3BitStoreServiceIT.java} | 2 +- .../bitstore/S3BitStoreServiceTest.java | 480 ------------------ 2 files changed, 1 insertion(+), 481 deletions(-) rename dspace-api/src/test/java/org/dspace/storage/bitstore/{S3BitStoreServiceIntegrationTest.java => S3BitStoreServiceIT.java} (99%) delete mode 100644 dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java similarity index 99% rename from dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java rename to dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java index 0ed321050a1c..f757a746ab89 100644 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceIT.java @@ -60,7 +60,7 @@ /** * @author Luca Giamminonni (luca.giamminonni at 4science.com) */ -public class S3BitStoreServiceIntegrationTest extends AbstractIntegrationTestWithDatabase { +public class S3BitStoreServiceIT extends AbstractIntegrationTestWithDatabase { private static final String DEFAULT_BUCKET_NAME = "dspace-asset-localhost"; diff --git a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java b/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java deleted file mode 100644 index 920fb9316ccc..000000000000 --- a/dspace-api/src/test/java/org/dspace/storage/bitstore/S3BitStoreServiceTest.java +++ /dev/null @@ -1,480 +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.storage.bitstore; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.isEmptyOrNullString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.startsWith; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.function.Supplier; - -import com.amazonaws.regions.Regions; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.GetObjectRequest; -import com.amazonaws.services.s3.model.PutObjectRequest; -import com.amazonaws.services.s3.model.PutObjectResult; -import com.amazonaws.services.s3.model.S3Object; -import com.amazonaws.services.s3.model.S3ObjectInputStream; -import com.amazonaws.services.s3.transfer.TransferManager; -import com.amazonaws.services.s3.transfer.Upload; -import com.amazonaws.services.s3.transfer.model.UploadResult; -import org.apache.commons.io.FileUtils; -import org.dspace.AbstractUnitTest; -import org.dspace.content.Bitstream; -import org.dspace.curate.Utils; -import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; - - - - -/** - * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) - * - */ -public class S3BitStoreServiceTest extends AbstractUnitTest { - - private S3BitStoreService s3BitStoreService; - - @Mock - private AmazonS3Client s3Service; - - @Mock - private TransferManager tm; - - @Mock - private Bitstream bitstream; - - @Mock - private Bitstream externalBitstream; - - @Before - public void setUp() throws Exception { - this.s3BitStoreService = new S3BitStoreService(s3Service, tm); - } - - private Supplier mockedServiceSupplier() { - return () -> this.s3Service; - } - - @Test - public void givenBucketWhenInitThenUsesSameBucket() throws IOException { - String bucketName = "Bucket0"; - s3BitStoreService.setBucketName(bucketName); - when(this.s3Service.doesBucketExist(bucketName)).thenReturn(false); - - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - - this.s3BitStoreService.init(); - - verify(this.s3Service).doesBucketExist(bucketName); - verify(this.s3Service, Mockito.times(1)).createBucket(bucketName); - assertThat(s3BitStoreService.getAwsAccessKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsSecretKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - } - - @Test - public void givenEmptyBucketWhenInitThenUsesDefaultBucket() throws IOException { - assertThat(s3BitStoreService.getBucketName(), isEmptyOrNullString()); - when(this.s3Service.doesBucketExist(startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX))).thenReturn(false); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - - this.s3BitStoreService.init(); - - verify(this.s3Service, Mockito.times(1)).createBucket(startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX)); - assertThat(s3BitStoreService.getBucketName(), Matchers.startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX)); - assertThat(s3BitStoreService.getAwsAccessKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsSecretKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - } - - @Test - public void givenAccessKeysWhenInitThenVerifiesCorrectBuilderCreation() throws IOException { - assertThat(s3BitStoreService.getAwsAccessKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsSecretKey(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getBucketName(), isEmptyOrNullString()); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - when(this.s3Service.doesBucketExist(startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX))).thenReturn(false); - - final String awsAccessKey = "ACCESS_KEY"; - final String awsSecretKey = "SECRET_KEY"; - - this.s3BitStoreService.setAwsAccessKey(awsAccessKey); - this.s3BitStoreService.setAwsSecretKey(awsSecretKey); - - try (MockedStatic mockedS3BitStore = Mockito.mockStatic(S3BitStoreService.class)) { - mockedS3BitStore - .when(() -> - S3BitStoreService.amazonClientBuilderBy( - ArgumentMatchers.any(Regions.class), - ArgumentMatchers.argThat( - credentials -> - awsAccessKey.equals(credentials.getAWSAccessKeyId()) && - awsSecretKey.equals(credentials.getAWSSecretKey()) - ) - ) - ) - .thenReturn(this.mockedServiceSupplier()); - - this.s3BitStoreService.init(); - - mockedS3BitStore.verify( - () -> - S3BitStoreService.amazonClientBuilderBy( - ArgumentMatchers.any(Regions.class), - ArgumentMatchers.argThat( - credentials -> - awsAccessKey.equals(credentials.getAWSAccessKeyId()) && - awsSecretKey.equals(credentials.getAWSSecretKey()) - ) - ) - ); - } - - - verify(this.s3Service, Mockito.times(1)).createBucket(startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX)); - assertThat(s3BitStoreService.getBucketName(), Matchers.startsWith(S3BitStoreService.DEFAULT_BUCKET_PREFIX)); - assertThat(s3BitStoreService.getAwsAccessKey(), Matchers.equalTo(awsAccessKey)); - assertThat(s3BitStoreService.getAwsSecretKey(), Matchers.equalTo(awsSecretKey)); - assertThat(s3BitStoreService.getAwsRegionName(), isEmptyOrNullString()); - } - - @Test - public void givenBucketBitStreamIdInputStreamWhenRetrievingFromS3ThenUsesBucketBitStreamId() throws IOException { - String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - S3Object object = Mockito.mock(S3Object.class); - S3ObjectInputStream inputStream = Mockito.mock(S3ObjectInputStream.class); - when(object.getObjectContent()).thenReturn(inputStream); - when(this.s3Service.getObject(ArgumentMatchers.any(GetObjectRequest.class))).thenReturn(object); - - this.s3BitStoreService.init(); - assertThat(this.s3BitStoreService.get(bitstream), Matchers.equalTo(inputStream)); - - verify(this.s3Service).getObject( - ArgumentMatchers.argThat( - request -> - bucketName.contentEquals(request.getBucketName()) && - bitStreamId.contentEquals(request.getKey()) - ) - ); - - } - - @Test - public void givenBucketBitStreamIdWhenNothingFoundOnS3ThenReturnsNull() throws IOException { - String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - when(this.s3Service.getObject(ArgumentMatchers.any(GetObjectRequest.class))).thenReturn(null); - - this.s3BitStoreService.init(); - assertThat(this.s3BitStoreService.get(bitstream), Matchers.nullValue()); - - verify(this.s3Service).getObject( - ArgumentMatchers.argThat( - request -> - bucketName.contentEquals(request.getBucketName()) && - bitStreamId.contentEquals(request.getKey()) - ) - ); - - } - - @Test - public void givenSubFolderWhenRequestsItemFromS3ThenTheIdentifierShouldHaveProperPath() throws IOException { - String bucketName = "BucketTest"; - String bitStreamId = "012345"; - String subfolder = "/test/DSpace7/"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - this.s3BitStoreService.setSubfolder(subfolder); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - S3Object object = Mockito.mock(S3Object.class); - S3ObjectInputStream inputStream = Mockito.mock(S3ObjectInputStream.class); - when(object.getObjectContent()).thenReturn(inputStream); - when(this.s3Service.getObject(ArgumentMatchers.any(GetObjectRequest.class))).thenReturn(object); - - this.s3BitStoreService.init(); - assertThat(this.s3BitStoreService.get(bitstream), Matchers.equalTo(inputStream)); - - verify(this.s3Service).getObject( - ArgumentMatchers.argThat( - request -> - bucketName.equals(request.getBucketName()) && - request.getKey().startsWith(subfolder) && - request.getKey().contains(bitStreamId) && - !request.getKey().contains(File.separator + File.separator) - ) - ); - - } - - @Test - public void handleRegisteredIdentifierPrefixInS3() { - String trueBitStreamId = "012345"; - String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + trueBitStreamId; - // Should be detected as registered bitstream - assertTrue(this.s3BitStoreService.isRegisteredBitstream(registeredBitstreamId)); - } - - @Test - public void stripRegisteredBitstreamPrefixWhenCalculatingPath() { - // Set paths and IDs - String s3Path = "UNIQUE_S3_PATH/test/bitstream.pdf"; - String registeredBitstreamId = s3BitStoreService.REGISTERED_FLAG + s3Path; - // Paths should be equal, since the getRelativePath method should strip the registered -R prefix - String relativeRegisteredPath = this.s3BitStoreService.getRelativePath(registeredBitstreamId); - assertEquals(s3Path, relativeRegisteredPath); - } - - @Test - public void givenBitStreamIdentifierLongerThanPossibleWhenIntermediatePathIsComputedThenIsSplittedAndTruncated() { - String path = "01234567890123456789"; - String computedPath = this.s3BitStoreService.getIntermediatePath(path); - String expectedPath = "01" + File.separator + "23" + File.separator + "45" + File.separator; - assertThat(computedPath, equalTo(expectedPath)); - } - - @Test - public void givenBitStreamIdentifierShorterThanAFolderLengthWhenIntermediatePathIsComputedThenIsSingleFolder() { - String path = "0"; - String computedPath = this.s3BitStoreService.getIntermediatePath(path); - String expectedPath = "0" + File.separator; - assertThat(computedPath, equalTo(expectedPath)); - } - - @Test - public void givenPartialBitStreamIdentifierWhenIntermediatePathIsComputedThenIsCompletlySplitted() { - String path = "01234"; - String computedPath = this.s3BitStoreService.getIntermediatePath(path); - String expectedPath = "01" + File.separator + "23" + File.separator + "4" + File.separator; - assertThat(computedPath, equalTo(expectedPath)); - } - - @Test - public void givenMaxLengthBitStreamIdentifierWhenIntermediatePathIsComputedThenIsSplittedAllAsSubfolder() { - String path = "012345"; - String computedPath = this.s3BitStoreService.getIntermediatePath(path); - String expectedPath = "01" + File.separator + "23" + File.separator + "45" + File.separator; - assertThat(computedPath, equalTo(expectedPath)); - } - - @Test - public void givenBitStreamIdentifierWhenIntermediatePathIsComputedThenNotEndingDoubleSlash() throws IOException { - StringBuilder path = new StringBuilder("01"); - String computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - int slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("2"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); - - path.append("3"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); - - path.append("4"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); - - path.append("56789"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator + File.separator))); - } - - @Test - public void givenBitStreamIdentidierWhenIntermediatePathIsComputedThenMustBeSplitted() throws IOException { - StringBuilder path = new StringBuilder("01"); - String computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - int slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("2"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("3"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("4"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - - path.append("56789"); - computedPath = this.s3BitStoreService.getIntermediatePath(path.toString()); - slashes = computeSlashes(path.toString()); - assertThat(computedPath, Matchers.endsWith(File.separator)); - assertThat(computedPath.split(File.separator).length, Matchers.equalTo(slashes)); - } - - @Test - public void givenBitStreamIdentifierWithSlashesWhenSanitizedThenSlashesMustBeRemoved() { - String sInternalId = new StringBuilder("01") - .append(File.separator) - .append("22") - .append(File.separator) - .append("33") - .append(File.separator) - .append("4455") - .toString(); - String computedPath = this.s3BitStoreService.sanitizeIdentifier(sInternalId); - assertThat(computedPath, Matchers.not(Matchers.startsWith(File.separator))); - assertThat(computedPath, Matchers.not(Matchers.endsWith(File.separator))); - assertThat(computedPath, Matchers.not(Matchers.containsString(File.separator))); - } - - @Test - public void givenBitStreamWhenRemoveThenCallS3DeleteMethod() throws Exception { - String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - this.s3BitStoreService.init(); - this.s3BitStoreService.remove(bitstream); - - verify(this.s3Service, Mockito.times(1)).deleteObject(ArgumentMatchers.eq(bucketName), - ArgumentMatchers.eq(bitStreamId)); - - } - - @Test - public void givenBitStreamWhenPutThenCallS3PutMethodAndStoresInBitStream() throws Exception { - String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - File file = Mockito.mock(File.class); - InputStream in = Mockito.mock(InputStream.class); - PutObjectResult putObjectResult = Mockito.mock(PutObjectResult.class); - Upload upload = Mockito.mock(Upload.class); - UploadResult uploadResult = Mockito.mock(UploadResult.class); - when(upload.waitForUploadResult()).thenReturn(uploadResult); - String mockedTag = "1a7771d5fdd7bfdfc84033c70b1ba555"; - when(file.length()).thenReturn(8L); - try (MockedStatic fileMock = Mockito.mockStatic(File.class)) { - try (MockedStatic fileUtilsMock = Mockito.mockStatic(FileUtils.class)) { - try (MockedStatic curateUtils = Mockito.mockStatic(Utils.class)) { - curateUtils.when(() -> Utils.checksum((File) ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenReturn(mockedTag); - - fileMock - .when(() -> File.createTempFile(ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenReturn(file); - - when(this.tm.upload(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenReturn(upload); - - this.s3BitStoreService.init(); - this.s3BitStoreService.put(bitstream, in); - } - } - - } - - verify(this.bitstream, Mockito.times(1)).setSizeBytes( - ArgumentMatchers.eq(8L) - ); - - verify(this.bitstream, Mockito.times(1)).setChecksum( - ArgumentMatchers.eq(mockedTag) - ); - - verify(this.tm, Mockito.times(1)).upload( - ArgumentMatchers.eq(bucketName), - ArgumentMatchers.eq(bitStreamId), - ArgumentMatchers.eq(file) - ); - - verify(file, Mockito.times(1)).delete(); - - } - - @Test - public void givenBitStreamWhenCallingPutFileCopyingThrowsIOExceptionPutThenFileIsRemovedAndStreamClosed() - throws Exception { - String bucketName = "BucketTest"; - String bitStreamId = "BitStreamId"; - this.s3BitStoreService.setBucketName(bucketName); - this.s3BitStoreService.setUseRelativePath(false); - when(bitstream.getInternalId()).thenReturn(bitStreamId); - - File file = Mockito.mock(File.class); - InputStream in = Mockito.mock(InputStream.class); - try (MockedStatic fileMock = Mockito.mockStatic(File.class)) { - try (MockedStatic fileUtilsMock = Mockito.mockStatic(FileUtils.class)) { - fileUtilsMock - .when(() -> FileUtils.copyInputStreamToFile(ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenThrow(IOException.class); - fileMock - .when(() -> File.createTempFile(ArgumentMatchers.any(), ArgumentMatchers.any())) - .thenReturn(file); - - this.s3BitStoreService.init(); - assertThrows(IOException.class, () -> this.s3BitStoreService.put(bitstream, in)); - } - - } - - verify(this.bitstream, Mockito.never()).setSizeBytes(ArgumentMatchers.any(Long.class)); - - verify(this.bitstream, Mockito.never()).setChecksum(ArgumentMatchers.any(String.class)); - - verify(this.s3Service, Mockito.never()).putObject(ArgumentMatchers.any(PutObjectRequest.class)); - - verify(file, Mockito.times(1)).delete(); - - } - - private int computeSlashes(String internalId) { - int minimum = internalId.length(); - int slashesPerLevel = minimum / S3BitStoreService.digitsPerLevel; - int odd = Math.min(1, minimum % S3BitStoreService.digitsPerLevel); - int slashes = slashesPerLevel + odd; - return Math.min(slashes, S3BitStoreService.directoryLevels); - } - -} From 39d32ba4c46b4c3958a52aaaed33edabff7b8a9e Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 15 May 2023 12:21:26 +0200 Subject: [PATCH 336/394] [DSC-1030] Added @Ignore to some ItemServiceTest tests --- .../java/org/dspace/content/service/ItemServiceTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java index 6ea5bc5b784b..5881287fb0c1 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java @@ -60,6 +60,7 @@ import org.dspace.versioning.service.VersioningService; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.orcid.jaxb.model.v3.release.search.expanded.ExpandedSearch; @@ -688,6 +689,7 @@ public void testFindItemsWithEditNoRights() throws Exception { } @Test + @Ignore public void testFindAndCountItemsWithEditEPerson() throws Exception { ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) .withUser(eperson) @@ -702,6 +704,7 @@ public void testFindAndCountItemsWithEditEPerson() throws Exception { } @Test + @Ignore public void testFindAndCountItemsWithAdminEPerson() throws Exception { ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) .withUser(eperson) @@ -716,6 +719,7 @@ public void testFindAndCountItemsWithAdminEPerson() throws Exception { } @Test + @Ignore public void testFindAndCountItemsWithEditGroup() throws Exception { context.turnOffAuthorisationSystem(); Group group = GroupBuilder.createGroup(context) @@ -736,6 +740,7 @@ public void testFindAndCountItemsWithEditGroup() throws Exception { } @Test + @Ignore public void testFindAndCountItemsWithAdminGroup() throws Exception { context.turnOffAuthorisationSystem(); Group group = GroupBuilder.createGroup(context) From 8bac4da3b2d92b5761be0cfd15dc32a42206ec91 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 16 May 2023 12:26:22 +0200 Subject: [PATCH 337/394] [DSC-1030] Fixed some tests --- .../content/service/ItemServiceTest.java | 5 -- .../rest/SystemWideAlertRestRepositoryIT.java | 63 +++++++++---------- dspace/config/spring/api/discovery.xml | 1 + 3 files changed, 29 insertions(+), 40 deletions(-) diff --git a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java index 5881287fb0c1..6ea5bc5b784b 100644 --- a/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/service/ItemServiceTest.java @@ -60,7 +60,6 @@ import org.dspace.versioning.service.VersioningService; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.orcid.jaxb.model.v3.release.search.expanded.ExpandedSearch; @@ -689,7 +688,6 @@ public void testFindItemsWithEditNoRights() throws Exception { } @Test - @Ignore public void testFindAndCountItemsWithEditEPerson() throws Exception { ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) .withUser(eperson) @@ -704,7 +702,6 @@ public void testFindAndCountItemsWithEditEPerson() throws Exception { } @Test - @Ignore public void testFindAndCountItemsWithAdminEPerson() throws Exception { ResourcePolicy rp = ResourcePolicyBuilder.createResourcePolicy(context) .withUser(eperson) @@ -719,7 +716,6 @@ public void testFindAndCountItemsWithAdminEPerson() throws Exception { } @Test - @Ignore public void testFindAndCountItemsWithEditGroup() throws Exception { context.turnOffAuthorisationSystem(); Group group = GroupBuilder.createGroup(context) @@ -740,7 +736,6 @@ public void testFindAndCountItemsWithEditGroup() throws Exception { } @Test - @Ignore public void testFindAndCountItemsWithAdminGroup() throws Exception { context.turnOffAuthorisationSystem(); Group group = GroupBuilder.createGroup(context) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java index beb979dfe68f..522c47670429 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SystemWideAlertRestRepositoryIT.java @@ -9,22 +9,22 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.matcher.DateMatcher.dateMatcher; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.text.DateFormat; -import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.time.DateUtils; import org.dspace.alerts.AllowSessionsEnum; import org.dspace.alerts.SystemWideAlert; import org.dspace.app.rest.model.SystemWideAlertRest; @@ -42,11 +42,11 @@ public void findAllTest() throws Exception { // Create two alert entries in the db to fully test the findAll method // Note: It is not possible to create two alerts through the REST API context.turnOffAuthorisationSystem(); - Date countdownDate = new Date(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") .withAllowSessions( AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) - .withCountdownDate(countdownDate) + .withCountdownDate(dateToNearestSecond) .isActive(true) .build(); @@ -58,8 +58,6 @@ public void findAllTest() throws Exception { .build(); context.restoreAuthSystemState(); - DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(get("/api/system/systemwidealerts/")) @@ -69,8 +67,7 @@ public void findAllTest() throws Exception { hasJsonPath("$.alertId", is(systemWideAlert1.getID())), hasJsonPath("$.message", is(systemWideAlert1.getMessage())), hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions().getValue())), - hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + hasJsonPath("$.countdownTo", dateMatcher(dateToNearestSecond)), hasJsonPath("$.active", is(systemWideAlert1.isActive())) ), allOf( @@ -141,11 +138,11 @@ public void findOneTest() throws Exception { // Create two alert entries in the db to fully test the findOne method // Note: It is not possible to create two alerts through the REST API context.turnOffAuthorisationSystem(); - Date countdownDate = new Date(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") .withAllowSessions( AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) - .withCountdownDate(countdownDate) + .withCountdownDate(dateToNearestSecond) .isActive(true) .build(); SystemWideAlert systemWideAlert2 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 2") @@ -156,7 +153,6 @@ public void findOneTest() throws Exception { .build(); context.restoreAuthSystemState(); - DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); String authToken = getAuthToken(admin.getEmail(), password); @@ -170,7 +166,7 @@ public void findOneTest() throws Exception { hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions().getValue())), hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + dateMatcher(dateToNearestSecond)), hasJsonPath("$.active", is(systemWideAlert1.isActive())) ) )); @@ -183,11 +179,11 @@ public void findOneUnauthorizedTest() throws Exception { // Create two alert entries in the db to fully test the findOne method // Note: It is not possible to create two alerts through the REST API context.turnOffAuthorisationSystem(); - Date countdownDate = new Date(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") .withAllowSessions( AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) - .withCountdownDate(countdownDate) + .withCountdownDate(dateToNearestSecond) .isActive(true) .build(); @@ -199,8 +195,6 @@ public void findOneUnauthorizedTest() throws Exception { .build(); context.restoreAuthSystemState(); - DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - // When the alert is active and the user is not an admin, the user will be able to see the alert getClient().perform(get("/api/system/systemwidealerts/" + systemWideAlert1.getID())) .andExpect(status().isOk()) @@ -211,7 +205,7 @@ public void findOneUnauthorizedTest() throws Exception { hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions().getValue())), hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + dateMatcher(dateToNearestSecond)), hasJsonPath("$.active", is(systemWideAlert1.isActive())) ) )); @@ -228,11 +222,11 @@ public void findOneForbiddenTest() throws Exception { // Create two alert entries in the db to fully test the findOne method // Note: It is not possible to create two alerts through the REST API context.turnOffAuthorisationSystem(); - Date countdownDate = new Date(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") .withAllowSessions( AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) - .withCountdownDate(countdownDate) + .withCountdownDate(dateToNearestSecond) .isActive(true) .build(); @@ -244,7 +238,6 @@ public void findOneForbiddenTest() throws Exception { .build(); context.restoreAuthSystemState(); - DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); String authToken = getAuthToken(eperson.getEmail(), password); @@ -257,7 +250,7 @@ public void findOneForbiddenTest() throws Exception { hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions().getValue())), hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + dateMatcher(dateToNearestSecond)), hasJsonPath("$.active", is(systemWideAlert1.isActive())) ) )); @@ -274,11 +267,11 @@ public void findAllActiveTest() throws Exception { // Create three alert entries in the db to fully test the findActive search method // Note: It is not possible to create two alerts through the REST API context.turnOffAuthorisationSystem(); - Date countdownDate = new Date(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); SystemWideAlert systemWideAlert1 = SystemWideAlertBuilder.createSystemWideAlert(context, "Test alert 1") .withAllowSessions( AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY) - .withCountdownDate(countdownDate) + .withCountdownDate(dateToNearestSecond) .isActive(true) .build(); @@ -297,8 +290,6 @@ public void findAllActiveTest() throws Exception { .build(); context.restoreAuthSystemState(); - DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - getClient().perform(get("/api/system/systemwidealerts/search/active")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.systemwidealerts", containsInAnyOrder( @@ -307,7 +298,7 @@ public void findAllActiveTest() throws Exception { hasJsonPath("$.message", is(systemWideAlert1.getMessage())), hasJsonPath("$.allowSessions", is(systemWideAlert1.getAllowSessions().getValue())), hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlert1.getCountdownTo()))), + dateMatcher(dateToNearestSecond)), hasJsonPath("$.active", is(systemWideAlert1.isActive())) ), allOf( @@ -323,9 +314,11 @@ public void findAllActiveTest() throws Exception { @Test public void createTest() throws Exception { + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); systemWideAlertRest.setMessage("Alert test message"); - systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setCountdownTo(dateToNearestSecond); systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); systemWideAlertRest.setActive(true); @@ -336,7 +329,6 @@ public void createTest() throws Exception { AtomicReference idRef = new AtomicReference<>(); - DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); getClient(authToken).perform(post("/api/system/systemwidealerts/") .content(mapper.writeValueAsBytes(systemWideAlertRest)) .contentType(contentType)) @@ -348,7 +340,7 @@ public void createTest() throws Exception { hasJsonPath("$.allowSessions", is(systemWideAlertRest.getAllowSessions())), hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlertRest.getCountdownTo()))), + dateMatcher(dateToNearestSecond)), hasJsonPath("$.active", is(systemWideAlertRest.isActive())) ) )) @@ -363,7 +355,7 @@ public void createTest() throws Exception { hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), hasJsonPath("$.allowSessions", is(systemWideAlertRest.getAllowSessions())), hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlertRest.getCountdownTo()))), + dateMatcher(dateToNearestSecond)), hasJsonPath("$.active", is(systemWideAlertRest.isActive())) ) )); @@ -450,10 +442,12 @@ public void putTest() throws Exception { .build(); context.restoreAuthSystemState(); + Date dateToNearestSecond = DateUtils.round(new Date(), Calendar.SECOND); + SystemWideAlertRest systemWideAlertRest = new SystemWideAlertRest(); systemWideAlertRest.setAlertId(systemWideAlert.getID()); systemWideAlertRest.setMessage("Updated alert test message"); - systemWideAlertRest.setCountdownTo(new Date()); + systemWideAlertRest.setCountdownTo(dateToNearestSecond); systemWideAlertRest.setAllowSessions(AllowSessionsEnum.ALLOW_CURRENT_SESSIONS_ONLY.getValue()); systemWideAlertRest.setActive(true); @@ -462,7 +456,6 @@ public void putTest() throws Exception { String authToken = getAuthToken(admin.getEmail(), password); - DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); getClient(authToken).perform(put("/api/system/systemwidealerts/" + systemWideAlert.getID()) .content(mapper.writeValueAsBytes(systemWideAlertRest)) .contentType(contentType)) @@ -474,7 +467,7 @@ public void putTest() throws Exception { hasJsonPath("$.allowSessions", is(systemWideAlertRest.getAllowSessions())), hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlertRest.getCountdownTo()))), + dateMatcher(dateToNearestSecond)), hasJsonPath("$.active", is(systemWideAlertRest.isActive())) ) )); @@ -487,7 +480,7 @@ public void putTest() throws Exception { hasJsonPath("$.message", is(systemWideAlertRest.getMessage())), hasJsonPath("$.allowSessions", is(systemWideAlertRest.getAllowSessions())), hasJsonPath("$.countdownTo", - startsWith(sdf.format(systemWideAlertRest.getCountdownTo()))), + dateMatcher(dateToNearestSecond)), hasJsonPath("$.active", is(systemWideAlertRest.isActive())) ) )); diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 12c8eb0c95eb..bde0b3f0184f 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -34,6 +34,7 @@ + From c19b6c4003655c500be45577498dd5c60f112ead Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 16 May 2023 17:13:26 +0200 Subject: [PATCH 338/394] [DSC-1030] Fixed DiscoveryRestControllerIT tests --- .../discovery/utils/DiscoverQueryBuilder.java | 8 +- .../app/rest/DiscoveryRestControllerIT.java | 1900 ++++++++++++----- dspace/config/spring/api/discovery.xml | 93 +- 3 files changed, 1450 insertions(+), 551 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java b/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java index 770434e09b10..ca067452ab6c 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java +++ b/dspace-api/src/main/java/org/dspace/discovery/utils/DiscoverQueryBuilder.java @@ -272,10 +272,10 @@ private void fillFacetIntoQueryArgs(Context context, IndexableObject scope, Stri // "show more" url int facetLimit = pageSize + 1; //This should take care of the sorting for us - prefix = StringUtils.isNotBlank(prefix) ? prefix.toLowerCase() : null; - queryArgs.addFacetField(new DiscoverFacetField(facet.getIndexFieldName(), facet.getType(), facetLimit, - facet.getSortOrderSidebar(), - StringUtils.trimToNull(prefix))); + queryArgs.addFacetField(new DiscoverFacetField(indexFieldName, facet.getType(), facetLimit, + facet.getSortOrderSidebar(), StringUtils.trimToNull(prefix), + facet.exposeMore(), facet.exposeMissing(), facet.exposeTotalElements(), facet.fillDateGaps(), + facet.inverseDirection())); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index 8b01e7b37790..64c1907a5a46 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -15,22 +15,29 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.emptyOrNullString; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Optional; import java.util.UUID; import com.jayway.jsonpath.matchers.JsonPathMatchers; import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.common.SolrDocument; import org.dspace.app.rest.matcher.AppliedFilterMatcher; import org.dspace.app.rest.matcher.FacetEntryMatcher; import org.dspace.app.rest.matcher.FacetValueMatcher; @@ -47,26 +54,40 @@ import org.dspace.builder.CollectionBuilder; import org.dspace.builder.CommunityBuilder; import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.EntityTypeBuilder; import org.dspace.builder.GroupBuilder; import org.dspace.builder.ItemBuilder; import org.dspace.builder.PoolTaskBuilder; +import org.dspace.builder.RelationshipBuilder; +import org.dspace.builder.RelationshipTypeBuilder; import org.dspace.builder.SupervisionOrderBuilder; import org.dspace.builder.WorkflowItemBuilder; import org.dspace.builder.WorkspaceItemBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.EntityType; import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.authority.Choices; import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.content.authority.service.MetadataAuthorityService; +import org.dspace.content.service.EntityTypeService; +import org.dspace.core.CrisConstants; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.configuration.DiscoveryConfigurationService; import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration; +import org.dspace.discovery.configuration.GraphDiscoverSearchFilterFacet; +import org.dspace.discovery.indexobject.ItemIndexFactoryImpl; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.supervision.SupervisionOrder; +import org.dspace.util.UUIDUtils; +import org.dspace.utils.DSpace; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; @@ -82,6 +103,12 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @Autowired MetadataAuthorityService metadataAuthorityService; + @Autowired + private DiscoveryConfigurationService discoveryConfigurationService; + + @Autowired + private EntityTypeService entityTypeService; + @Autowired ChoiceAuthorityService choiceAuthorityService; @@ -117,11 +144,15 @@ public void discoverFacetsTestWithoutParameters() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets"))) //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.subjectFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false))) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false))) ); } @@ -183,7 +214,10 @@ public void discoverFacetsAuthorTestWithSizeParameter() throws Exception { .andExpect(jsonPath("$.facetType", is("text"))) //Because we've constructed such a structure so that we have more than 2 (size) authors, there // needs to be a next link - .andExpect(jsonPath("$._links.next.href", containsString("api/discover/facets/author?page"))) + .andExpect(jsonPath("$._links.next.href", + Matchers.allOf( + containsString("api/discover/facets/author"), + containsString("page=1")))) //There always needs to be a self link .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets/author"))) //Because there are more authors than is represented (because of the size param), hasMore has to @@ -203,8 +237,13 @@ public void discoverFacetsAuthorTestWithSizeParameter() throws Exception { @Test public void discoverFacetsAuthorWithAuthorityWithSizeParameter() throws Exception { + configurationService.setProperty("choices.plugin.dc.contributor.author", "SolrAuthorAuthority"); configurationService.setProperty("authority.controlled.dc.contributor.author", "true"); + configurationService.setProperty("discovery.browse.authority.ignore-preferred.author", true); + configurationService.setProperty("discovery.index.authority.ignore-preferred.dc.contributor.author", true); + configurationService.setProperty("discovery.browse.authority.ignore-variants.author", true); + configurationService.setProperty("discovery.index.authority.ignore-variants.dc.contributor.author", true); metadataAuthorityService.clearCache(); choiceAuthorityService.clearCache(); @@ -270,7 +309,8 @@ public void discoverFacetsAuthorWithAuthorityWithSizeParameter() throws Exceptio .andExpect(jsonPath("$.name", is("author"))) //Because we've constructed such a structure so that we have more than 2 (size) subjects, there // needs to be a next link - .andExpect(jsonPath("$._links.next.href", containsString("api/discover/facets/author?page"))) + .andExpect(jsonPath("$._links.next.href", + containsString("api/discover/facets/author?configuration=defaultConfiguration&page"))) //There always needs to be a self link .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets/author"))) //Because there are more subjects than is represented (because of the size param), hasMore has to @@ -364,150 +404,6 @@ public void discoverFacetsAuthorTestWithPrefix() throws Exception { ; } - @Test - public void discoverFacetsAuthorTestWithPrefix_Capital_And_Special_Chars() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection").build(); - - Item publicItem1 = ItemBuilder.createItem(context, collection) - .withTitle("Public item 1") - .withIssueDate("2017-10-17") - .withAuthor("Smith, John") - .withAuthor("Jan, Doe") - .build(); - - Item publicItem2 = ItemBuilder.createItem(context, collection) - .withTitle("Public item 2") - .withIssueDate("2016-02-13") - .withAuthor("S’Idan, Mo") - .withAuthor("Tick&Tock") - .build(); - - Item publicItem3 = ItemBuilder.createItem(context, collection) - .withTitle("Public item 3") - .withIssueDate("2016-02-13") - .withAuthor("M Akai") - .withAuthor("stIjn, SmITH") - .build(); - - Item publicItem4 = ItemBuilder.createItem(context, collection) - .withTitle("Public item 4") - .withIssueDate("2012-05-13") - .withSubject("St Augustine") - .build(); - - Item publicItem5 = ItemBuilder.createItem(context, collection) - .withTitle("Public item 5") - .withIssueDate("2015-11-23") - .withSubject("Health & Medicine") - .build(); - - Item publicItem6 = ItemBuilder.createItem(context, collection) - .withTitle("Public item 6") - .withIssueDate("2003-07-11") - .withSubject("1% economy") - .build(); - - Item publicItem7 = ItemBuilder.createItem(context, collection) - .withTitle("Public item 7") - .withIssueDate("2008-12-31") - .withSubject("I.T.") - .build(); - - Item publicItem8 = ItemBuilder.createItem(context, collection) - .withTitle("Public item 8") - .withIssueDate("2013-07-21") - .withSubject("?Unknown") - .build(); - - context.restoreAuthSystemState(); - - // The prefix query for author queries should be case-insensitive and correctly handle special characters - - getClient().perform(get("/api/discover/facets/author") - .param("prefix", "Smith")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entryFacetWithoutSelfLink("Smith, John"), - FacetValueMatcher.entryFacetWithoutSelfLink("stIjn, SmITH")))); - - getClient().perform(get("/api/discover/facets/author") - .param("prefix", "S")) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher - .entryFacetWithoutSelfLink("Smith, John"), - FacetValueMatcher - .entryFacetWithoutSelfLink("S’Idan, Mo"), - // gets returned once for smith, once for stijn - FacetValueMatcher - .entryFacetWithoutSelfLink("stIjn, SmITH"), - FacetValueMatcher - .entryFacetWithoutSelfLink("stIjn, SmITH")))); - - getClient().perform(get("/api/discover/facets/author") - .param("prefix", "M A")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("M Akai")))); - - getClient().perform(get("/api/discover/facets/author") - .param("prefix", "S’I")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("S’Idan, Mo")))); - - getClient().perform(get("/api/discover/facets/author") - .param("prefix", "Jan, D")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("Jan, Doe")))); - - getClient().perform(get("/api/discover/facets/author") - .param("prefix", "Tick&")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("Tick&Tock")))); - - // Should also be the case for subject queries - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "St A")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher - .entryFacetWithoutSelfLink("St Augustine")))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "Health & M")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher - .entryFacetWithoutSelfLink("Health & Medicine")))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "1% e")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("1% economy")))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "I.")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("I.T.")))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "U")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", - containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("?Unknown")))); - } - @Test public void discoverFacetsAuthorTestWithPrefixFirstName() throws Exception { context.turnOffAuthorisationSystem(); @@ -729,7 +625,9 @@ public void discoverFacetsAuthorTestForPagination() throws Exception { .andExpect(jsonPath("$.facetType", is("text"))) //There needs to be a next link because there are more authors than the current size is allowed to // show. There are more pages after this one - .andExpect(jsonPath("$._links.next.href", containsString("api/discover/facets/author?page"))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + containsString("api/discover/facets/author"), + containsString("page=2")))) //There always needs to be a self link .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets/author"))) //The page object has to be like this because that's what we've asked in the parameters @@ -988,7 +886,8 @@ public void discoverFacetsTestWithScope() throws Exception { //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets/author?scope=testScope"))) .andExpect(jsonPath("$._links.next.href", - containsString("api/discover/facets/author?scope=testScope&page=1&size=2"))) + containsString( + "api/discover/facets/author?scope=testScope&configuration=defaultConfiguration&page=1&size=2"))) //These are the values that need to be present as it's ordered by count and these authors are the // most common ones in the items that we've created .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( @@ -1096,7 +995,9 @@ public void discoverFacetsDateTestForHasMore() throws Exception { .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets/dateIssued"))) //Seeing as we've entered a size of two and there are more dates than just two, we'll need a next // link to go to the next page to see the rest of the dates - .andExpect(jsonPath("$._links.next.href", containsString("api/discover/facets/dateIssued?page"))) + .andExpect(jsonPath("$._links.next.href", Matchers.allOf( + containsString("api/discover/facets/dateIssued"), + containsString("page=1")))) //The page object needs to look like this because we've entered a size of 2 and we didn't specify // a starting page so it defaults to 0 .andExpect(jsonPath("$.page", @@ -1209,6 +1110,9 @@ public void discoverSearchTest() throws Exception { //There needs to be a section where these filters as specified as they're the default filters // given in the configuration .andExpect(jsonPath("$.filters", containsInAnyOrder( + SearchFilterMatcher.barDateIssuedYearFilter(), + SearchFilterMatcher.pieItemtypeFilter(), + SearchFilterMatcher.typeFilter(), SearchFilterMatcher.titleFilter(), SearchFilterMatcher.authorFilter(), SearchFilterMatcher.subjectFilter(), @@ -1221,7 +1125,8 @@ public void discoverSearchTest() throws Exception { SearchFilterMatcher.isProjectOfPublicationRelation(), SearchFilterMatcher.isOrgUnitOfPublicationRelation(), SearchFilterMatcher.isPublicationOfJournalIssueRelation(), - SearchFilterMatcher.isJournalOfPublicationRelation() + SearchFilterMatcher.isJournalOfPublicationRelation(), + SearchFilterMatcher.languageFilter() ))) //These sortOptions need to be present as it's the default in the configuration .andExpect(jsonPath("$.sortOptions", contains( @@ -1229,8 +1134,14 @@ public void discoverSearchTest() throws Exception { "score", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), SortOptionMatcher.sortOptionMatcher( "dc.title", DiscoverySortFieldConfiguration.SORT_ORDER.asc.name()), + SortOptionMatcher.sortOptionMatcher( + "dc.title", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), + SortOptionMatcher.sortOptionMatcher( + "dc.date.issued", DiscoverySortFieldConfiguration.SORT_ORDER.asc.name()), SortOptionMatcher.sortOptionMatcher( "dc.date.issued", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()), + SortOptionMatcher.sortOptionMatcher( + "dc.date.accessioned", DiscoverySortFieldConfiguration.SORT_ORDER.asc.name()), SortOptionMatcher.sortOptionMatcher( "dc.date.accessioned", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()) ))); @@ -1346,17 +1257,12 @@ public void discoverSearchObjectsTest() throws Exception { //There needs to be a page object that shows the total pages and total elements as well as the // size and the current page (number) .andExpect(jsonPath("$._embedded.searchResult.page", is( - PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 7) + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3) ))) //These search results have to be shown in the embedded.objects section as these are the items // given in the structure defined above. //Seeing as everything fits onto one page, they have to all be present .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.hasItems( - SearchResultMatcher.match("core", "community", "communities"), - SearchResultMatcher.match("core", "community", "communities"), - //This has to be like this because collections don't have anything else - SearchResultMatcher.match(), - SearchResultMatcher.match(), SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items") @@ -1364,11 +1270,15 @@ public void discoverSearchObjectsTest() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -1410,6 +1320,7 @@ public void discoverSearchBrowsesWithSpecialCharacterTest() throws Exception { parentCommunity = CommunityBuilder.createCommunity(context).build(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity).build(); ItemBuilder.createItem(context, collection) + .withEntityType("Publication") .withAuthor("DSpace & friends") .build(); @@ -1479,21 +1390,15 @@ public void discoverSearchObjectsTestHasMoreAuthorFacet() throws Exception { .andExpect(status().isOk()) //The type has to be 'discover' .andExpect(jsonPath("$.type", is("discover"))) - //The page object has to look like this because we've only made 7 elements, the default size is 20 - // and they all fit onto one page (20 > 7) so totalPages has to be 1. Number is 0 because - //page 0 is the default page we view if not specified otherwise + //The page object has to look like this because we've only made 7 elements (3 items), + //the default size is 20 and they all fit onto one page (20 > 7) so totalPages has + //to be 1. Number is 0 because page 0 is the default page we view if not specified otherwise .andExpect(jsonPath("$._embedded.searchResult.page", is( - PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 7) + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3) ))) //All elements have to be present in the embedded.objects section, these are the ones we made in // the structure defined above .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.hasItems( - SearchResultMatcher.match("core", "community", "communities"), - SearchResultMatcher.match("core", "community", "communities"), - //Match without any parameters because collections don't have anything special to check in the - // json - SearchResultMatcher.match(), - SearchResultMatcher.match(), SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items") @@ -1503,11 +1408,15 @@ public void discoverSearchObjectsTestHasMoreAuthorFacet() throws Exception { //We do however exceed the limit for the authors, so this property has to be true for the author // facet .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(true), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -1572,18 +1481,13 @@ public void discoverSearchObjectsTestHasMoreSubjectFacet() throws Exception { .andExpect(status().isOk()) //The type has to be 'discover' .andExpect(jsonPath("$.type", is("discover"))) - //The page object has to look like this because we've only made 7 items, they all fit onto 1 page - // because the default size is 20 and the default starting page is 0. + //The page object has to look like this because we've only made 7 objects (but only 3 items), + //they all fit onto 1 page because the default size is 20 and the default starting page is 0. .andExpect(jsonPath("$._embedded.searchResult.page", is( - PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 7) + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3) ))) //All the elements created in the structure above have to be present in the embedded.objects section .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.hasItems( - SearchResultMatcher.match("core", "community", "communities"), - SearchResultMatcher.match("core", "community", "communities"), - //Collections are specified like this because they don't have any special properties - SearchResultMatcher.match(), - SearchResultMatcher.match(), SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items") @@ -1593,11 +1497,15 @@ public void discoverSearchObjectsTestHasMoreSubjectFacet() throws Exception { //We do however exceed the limit for the subject, so this property has to be true for the subject // facet .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(true), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -1673,11 +1581,15 @@ public void discoverSearchObjectsTestWithBasicQuery() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -1763,17 +1675,12 @@ public void discoverSearchObjectsTestWithScope() throws Exception { .andExpect(jsonPath("$.type", is("discover"))) //The page element has to look like this because it contains all the elements we've just created .andExpect(jsonPath("$._embedded.searchResult.page", is( - PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 7) + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3) ))) //The scope property has to be set to the value we entered in the parameters .andExpect(jsonPath("$.scope", is("test"))) //All the elements created in the structure above have to be present in the embedded.objects section .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.hasItems( - SearchResultMatcher.match("core", "community", "communities"), - SearchResultMatcher.match("core", "community", "communities"), - //Collections are specified like this because they don't have any special properties - SearchResultMatcher.match(), - SearchResultMatcher.match(), SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items") @@ -1781,11 +1688,15 @@ public void discoverSearchObjectsTestWithScope() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -1861,17 +1772,22 @@ public void discoverSearchObjectsTestWithDsoType() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); // With dsoTypes 'community' and 'collection' getClient().perform(get("/api/discover/search/objects") + .param("configuration", "backend") .param("dsoType", "Community") .param("dsoType", "Collection")) @@ -1896,17 +1812,22 @@ public void discoverSearchObjectsTestWithDsoType() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); // With dsoTypes 'collection' and 'item' getClient().perform(get("/api/discover/search/objects") + .param("configuration", "backend") .param("dsoType", "Collection") .param("dsoType", "Item")) @@ -1932,17 +1853,22 @@ public void discoverSearchObjectsTestWithDsoType() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); // With dsoTypes 'community', 'collection' and 'item' getClient().perform(get("/api/discover/search/objects") + .param("configuration", "backend") .param("dsoType", "Community") .param("dsoType", "Collection") .param("dsoType", "Item")) @@ -1972,11 +1898,15 @@ public void discoverSearchObjectsTestWithDsoType() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); @@ -2059,11 +1989,15 @@ public void discoverSearchObjectsTestWithDsoTypeAndSort() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //We want to get the sort that's been used as well in the response .andExpect(jsonPath("$.sort", is( @@ -2249,7 +2183,7 @@ public void discoverSearchObjectsTestForPaginationAndNextLinks() throws Exceptio //An anonymous user browses this endpoint to find the the objects in the system //With a size 2 getClient().perform(get("/api/discover/search/objects") - .param("size", "2") + .param("size", "1") .param("page", "1")) //** THEN ** //The status has to be 200 OK @@ -2257,25 +2191,30 @@ public void discoverSearchObjectsTestForPaginationAndNextLinks() throws Exceptio //The type has to be 'discover' .andExpect(jsonPath("$.type", is("discover"))) //The page object needs to look like this - //Size of 2 because that's what we entered + //Size of 1 because that's what we entered //Page number 1 because that's the param we entered - //TotalPages 4 because size = 2 and total elements is 7 -> 4 pages + //TotalPages 3 because size = 1 and total elements is 3 -> 3 pages //We made 7 elements -> 7 total elements + //dspace-cris excludes communities and collections by default, + //so we have 3 indexed elements (items) .andExpect(jsonPath("$._embedded.searchResult.page", is( - PageMatcher.pageEntryWithTotalPagesAndElements(1, 2, 4, 7) + PageMatcher.pageEntryWithTotalPagesAndElements(1, 1, 3, 3) ))) - //These are the two elements that'll be shown (because page = 1, so the third and fourth element - // in the list) and they'll be the only ones because the size is 2 + //These is the element that'll be shown (because page = 1, so the second element + // in the list) and it will be the only one because the size is 1 .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match(), SearchResultMatcher.match() ))) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(true), FacetEntryMatcher.subjectFacet(true), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false), - FacetEntryMatcher.entityTypeFacet(false) + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -2361,11 +2300,15 @@ public void discoverSearchObjectsTestWithContentInABitstream() throws Exception //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -2428,16 +2371,12 @@ public void discoverSearchObjectsTestForEmbargoedItemsAndPrivateItems() throws E //The type has to be 'discover' .andExpect(jsonPath("$.type", is("discover"))) //The page object needs to look like this + //dspace-cris default configuration returns only items .andExpect(jsonPath("$._embedded.searchResult.page", is( - PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 5) + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 1) ))) //These are the items that aren't set to private .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.hasItems( - SearchResultMatcher.match("core", "community", "communities"), - SearchResultMatcher.match("core", "community", "communities"), - //Collections are specified like this because they don't have any special properties - SearchResultMatcher.match(), - SearchResultMatcher.match(), SearchResultMatcher.matchOnItemName("item", "items", "Test") ))) //This is a private item, this shouldn't show up in the result @@ -2451,11 +2390,15 @@ public void discoverSearchObjectsTestForEmbargoedItemsAndPrivateItems() throws E //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -2614,11 +2557,15 @@ public void discoverSearchObjectsTestForScope() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -2699,11 +2646,15 @@ public void discoverSearchObjectsTestForScopeWithPrivateItem() throws Exception //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -2879,11 +2830,15 @@ public void discoverSearchObjectsTestForHitHighlights() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3023,11 +2978,15 @@ public void discoverSearchObjectsWithQueryOperatorContains_query() throws Except //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3099,11 +3058,15 @@ public void discoverSearchObjectsWithQueryOperatorContains() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3173,11 +3136,15 @@ public void discoverSearchObjectsWithQueryOperatorNotContains_query() throws Exc //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3248,11 +3215,15 @@ public void discoverSearchObjectsWithQueryOperatorNotContains() throws Exception //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3321,23 +3292,27 @@ public void discoverSearchObjectsTestForMinMaxValues() throws Exception { //The page object needs to look like this //Size of 2 because that's what we entered //Page number 1 because that's the param we entered - //TotalPages 4 because size = 2 and total elements is 7 -> 4 pages - //We made 7 elements -> 7 total elements + //TotalPages 2 because size = 2 and total elements is 3 -> 2 pages + //We made 7 elements but only items are included in the dspace-cris default configuration + // -> 3 total elements .andExpect(jsonPath("$._embedded.searchResult.page", is( - PageMatcher.pageEntryWithTotalPagesAndElements(1, 2, 4, 7) + PageMatcher.pageEntryWithTotalPagesAndElements(1, 2, 2, 3) ))) - //These are the two elements that'll be shown (because page = 1, so the third and fourth element - // in the list) and they'll be the only ones because the size is 2 + //These are the two elements that'll be shown (because page = 1, so the third element + // in the list) and they'll be the only one .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match(), SearchResultMatcher.match() ))) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacetWithMinMax(true, "Doe, Jane", "Testing, Works"), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(true), FacetEntryMatcher.dateIssuedFacetWithMinMax(false, "1990-02-13", "2010-10-17"), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3402,11 +3377,15 @@ public void discoverSearchFacetsTestForMinMaxValues() throws Exception { //The type has to be 'discover' .andExpect(jsonPath("$.type", is("discover"))) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacetWithMinMax(true, "Doe, Jane", "Testing, Works"), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(true), FacetEntryMatcher.dateIssuedFacetWithMinMax(false, "1990-02-13", "2010-10-17"), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/facets"))) @@ -3476,11 +3455,15 @@ public void discoverSearchObjectsWithQueryOperatorEquals_query() throws Exceptio //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3551,11 +3534,15 @@ public void discoverSearchObjectsWithQueryOperatorEquals() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3626,11 +3613,15 @@ public void discoverSearchObjectsWithQueryOperatorNotEquals_query() throws Excep //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3702,11 +3693,15 @@ public void discoverSearchObjectsWithQueryOperatorNotEquals() throws Exception { //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3776,11 +3771,15 @@ public void discoverSearchObjectsWithQueryOperatorNotAuthority_query() throws Ex //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3851,11 +3850,15 @@ public void discoverSearchObjectsWithQueryOperatorNotAuthority() throws Exceptio //These facets have to show up in the embedded.facets section as well with the given hasMore property // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), - FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -4276,33 +4279,30 @@ public void discoverSearchObjectsWithInProgressSubmissionTest() throws Exception //The type has to be 'discover' .andExpect(jsonPath("$.type", is("discover"))) //There needs to be a page object that shows the total pages and total elements as well as the - // size and the current page (number) + // size and the current page (number) - only items are included in the default dspace-cris conf .andExpect(jsonPath("$._embedded.searchResult.page", is( - PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 7) + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3) ))) //These search results have to be shown in the embedded.objects section as these are the items - // given in the structure defined above. + // given in the structure defined above (dspace-cris only return items by default). //Seeing as everything fits onto one page, they have to all be present .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( - SearchResultMatcher.match("core", "community", "communities"), - SearchResultMatcher.match("core", "community", "communities"), - SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items"), SearchResultMatcher.match("core", "item", "items"), - //This has to be like this because collections don't have anything else - // these matchers also need to be the last otherwise they will be potentially consumed for - // other staff - SearchResultMatcher.match(), - SearchResultMatcher.match() + SearchResultMatcher.match("core", "item", "items") ))) //These facets have to show up in the embedded.facets section as well with the given hasMore // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( + FacetEntryMatcher.anyFacet("graphitemtype", "chart.pie"), + FacetEntryMatcher.anyFacet("graphpubldate", "chart.bar"), + FacetEntryMatcher.typeFacet(false), FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false), - FacetEntryMatcher.entityTypeFacet(false) + FacetEntryMatcher.entityTypeFacet(false), + FacetEntryMatcher.languageFacet(false) ))) //There always needs to be a self link .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -5680,76 +5680,344 @@ public void discoverSearchObjectsTestForAdministrativeViewWithFiltersEquals() th } @Test - public void discoverSearchPoolTaskObjectsTest() throws Exception { + public void graphDiscoverSearchFilterFacetByDateTest() throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); + .withName("Parent Community") + .build(); - EPerson reviewer = EPersonBuilder.createEPerson(context) - .withEmail("reviewer1@example.com") - .withPassword(password).build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community").build(); - Collection col = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Collection 1") - .withWorkflowGroup(1, reviewer, admin).build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1").build(); - ItemBuilder.createItem(context, col) - .withTitle("Punnett square") - .withIssueDate("2016-02-13") - .withAuthor("Bandola, Roman") - .withSubject("ExtraEntry").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2").build(); - // create a normal user to use as submitter - EPerson submitter = EPersonBuilder.createEPerson(context) - .withEmail("submitter@example.com") - .withPassword(password).build(); + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Boychuk, Michele") + .withSubject("ExtraEntry").build(); - context.setCurrentUser(submitter); + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Boychuk, Michele") + .withAuthor("Bollini, Andrea") + .withSubject("ExtraEntry").build(); - PoolTaskBuilder.createPoolTask(context, col, reviewer) - .withTitle("Metaphysics") - .withIssueDate("2017-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry").build(); + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 3") + .withIssueDate("2017-10-17") + .withAuthor("Boychuk, Michele") + .withSubject("ExtraEntry").build(); - PoolTaskBuilder.createPoolTask(context, col, reviewer) - .withTitle("Mathematical Theory") - .withIssueDate("2020-01-19") - .withAuthor("Tommaso, Gattari") - .withSubject("ExtraEntry").build(); + Item publicItem4 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 4") + .withIssueDate("2020-02-13") + .withAuthor("Trus, Volodymyr") + .withSubject("AnotherTest") + .withSubject("ExtraEntry").build(); - PoolTaskBuilder.createPoolTask(context, col, reviewer) - .withTitle("Test Metaphysics") - .withIssueDate("2017-10-17") - .withAuthor("Smith, Donald") - .withSubject("ExtraEntry").build(); + Item publicItem5 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 5") + .withIssueDate("2019-06-17") + .withAuthor("Bruschetti, Luca") + .withSubject("ExtraEntry").build(); context.restoreAuthSystemState(); - String adminToken = getAuthToken(admin.getEmail(), password); + getClient().perform(get("/api/discover/facets/graphpubldate") + .param("size", "3")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$.name", is("graphpubldate"))) + .andExpect(jsonPath("$.facetType", is("chart.bar"))) + .andExpect(jsonPath("$._links.self.href", containsString( + "api/discover/facets/graphpubldate"))) + .andExpect(jsonPath("$.missing", is("0"))) + .andExpect(jsonPath("$.more", is("3"))) + .andExpect(jsonPath("$.totalElements", is("4"))) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 3)))) + .andExpect(jsonPath("$._embedded.values", contains( + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2020", 1), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2019", 1), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2018", 0)))); + + GraphDiscoverSearchFilterFacet graphpubldateFacet = + (GraphDiscoverSearchFilterFacet) discoveryConfigurationService.getDiscoveryConfiguration(null) + .getSidebarFacet("graphpubldate"); + + try { + // change the default configuration to disable the reverse direction + graphpubldateFacet.setInverseDirection(false); + graphpubldateFacet.setGraphType("bar.left-to-right"); + getClient().perform(get("/api/discover/facets/graphpubldate") + .param("size", "4")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$.name", is("graphpubldate"))) + .andExpect(jsonPath("$.facetType", is("chart.bar.left-to-right"))) + .andExpect(jsonPath("$._links.self.href", containsString( + "api/discover/facets/graphpubldate"))) + .andExpect(jsonPath("$.missing", is("0"))) + .andExpect(jsonPath("$.more", is("1"))) + .andExpect(jsonPath("$.totalElements", is("4"))) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 4)))) + .andExpect(jsonPath("$._embedded.values", contains( + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2016", 1), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2017", 2), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2018", 0), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2019", 1)))); + + // also disable the fillDateGap flag + graphpubldateFacet.setFillDateGaps(false); + graphpubldateFacet.setGraphType("bar.right-to-left"); + getClient().perform(get("/api/discover/facets/graphpubldate") + .param("size", "4")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$.name", is("graphpubldate"))) + .andExpect(jsonPath("$.facetType", is("chart.bar.right-to-left"))) + .andExpect(jsonPath("$._links.self.href", containsString( + "api/discover/facets/graphpubldate"))) + .andExpect(jsonPath("$.missing", is("0"))) + .andExpect(jsonPath("$.more", is("0"))) + .andExpect(jsonPath("$.totalElements", is("4"))) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 4)))) + .andExpect(jsonPath("$._embedded.values", contains( + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2016", 1), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2017", 2), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2019", 1), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("2020", 1)))); + } finally { + // restore default configuration + graphpubldateFacet.setInverseDirection(true); + graphpubldateFacet.setFillDateGaps(true); + graphpubldateFacet.setGraphType("bar"); + } + } - getClient(adminToken).perform(get("/api/discover/search/objects") - .param("configuration", "workflow") - .param("sort", "dc.date.issued,DESC") - .param("query", "Mathematical Theory")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.query", is("Mathematical Theory"))) - .andExpect(jsonPath("$.configuration", is("workflow"))) - .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.contains( - SearchResultMatcher.match("workflow", "pooltask", "pooltasks") - ))) - .andExpect(jsonPath("$._embedded.searchResult._embedded.objects",Matchers.contains( - allOf(hasJsonPath("$._embedded.indexableObject._embedded.workflowitem._embedded.item", - is(SearchResultMatcher.matchEmbeddedObjectOnItemName("item", "Mathematical Theory")))) - ))) - .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + @Test + public void graphDiscoverSearchFilterFacetByType() throws Exception { + context.turnOffAuthorisationSystem(); - getClient(adminToken).perform(get("/api/discover/search/objects") - .param("configuration", "workflow") - .param("sort", "dc.date.issued,DESC") - .param("query", "Metaphysics")) - .andExpect(status().isOk()) + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community").build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2").build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Boychuk, Michele") + .withType("book").build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Bollini, Andrea") + .withType("manuscript").build(); + + Item publicItem3 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 3") + .withIssueDate("2017-10-17") + .withAuthor("Boychuk, Michele") + .withSubject("AnotherTest") + .withType("manuscript").build(); + + Item publicItem4 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 4") + .withIssueDate("2020-02-13") + .withAuthor("Trus, Volodymyr") + .withSubject("AnotherTest") + .withType("Journal Article") + .withSubject("ExtraEntry").build(); + + Item publicItem5 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 5 without type") + .withIssueDate("2021-01-07") + .withAuthor("Volodymyr, Trus") + .withSubject("AnotherTest") + .withSubject("ExtraEntry").build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/graphitemtype") + .param("size", "2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$.name", is("graphitemtype"))) + .andExpect(jsonPath("$.facetType", is("chart.pie"))) + .andExpect(jsonPath("$.missing", is("1"))) + .andExpect(jsonPath("$._links.missing.href", + containsString("f.graphitemtype=%5B*%20TO%20*%5D,notequals"))) + .andExpect(jsonPath("$.more", is("2"))) + .andExpect(jsonPath("$._links.more.href", Matchers.allOf( + containsString("f.graphitemtype=manuscript,notequals"), + containsString("f.graphitemtype=book,notequals") + ))) + .andExpect(jsonPath("$.totalElements", is("3"))) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 2)))) + .andExpect(jsonPath("$._embedded.values", contains( + FacetValueMatcher.entryDateIssuedWithLabelAndCount("manuscript", 2), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("book", 1) + ))); + } + + @Test + public void nestedPlaceholderFacetTest() throws Exception { + context.turnOffAuthorisationSystem(); + final DSpace dSpace = new DSpace(); + SearchService searchService = dSpace.getSingletonService(SearchService.class); + ConfigurationService configurationService = dSpace.getConfigurationService(); + configurationService.setProperty("discovery.index.projection", "oairecerif.author.affiliation"); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community").build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withEntityType("Publication") + .build(); + + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Boychuk, Michele") + .withAuthorAffiliation("4Science") + .withAuthor("Another, Author") + .withAuthorAffiliationPlaceholder() + .withAuthor("Bollini, Andrea") + .withAuthorAffiliation("4Science") + .withType("book").build(); + + Item publicItem2 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Dohonue, Tim") + .withAuthorAffiliation("Lyrasis") + .withType("manuscript").build(); + + Item publicItem3 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 3") + .withIssueDate("2018-08-13") + .withAuthor("Dohonue, Tim") + .withAuthorAffiliationPlaceholder() + .withType("manuscript").build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/facets/organization") + .param("configuration", "researchoutputs") + .param("size", "3")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$.name", is("organization"))) + .andExpect(jsonPath("$.missing", is("1"))) + .andExpect(jsonPath("$._links.missing.href", + containsString("f.organization=%5B*%20TO%20*%5D,notequals"))) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 3)))) + .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( + FacetValueMatcher.entryText("organization", "4Science", 1), + FacetValueMatcher.entryText("organization", "Lyrasis", 1)))); + + QueryResponse qResp = searchService.getSolrSearchCore().getSolr() + .query(new SolrQuery("search.resourceid:" + publicItem1.getID().toString())); + final SolrDocument solrDocument = qResp.getResults().get(0); + assertThat((ArrayList) solrDocument.getFieldValue("oairecerif.author.affiliation_stored"), + Matchers.contains( + StringUtils.join(new String[] { "4Science", "null", "null", "null", "null" }, + ItemIndexFactoryImpl.STORE_SEPARATOR), + StringUtils.join(new String[] { + CrisConstants.PLACEHOLDER_PARENT_METADATA_VALUE, "null", "null", "null", "null" }, + ItemIndexFactoryImpl.STORE_SEPARATOR), + StringUtils.join(new String[] { "4Science", "null", "null", "null", "null" }, + ItemIndexFactoryImpl.STORE_SEPARATOR))); + } + + @Test + public void discoverSearchPoolTaskObjectsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + EPerson reviewer = EPersonBuilder.createEPerson(context) + .withEmail("reviewer1@example.com") + .withPassword(password).build(); + + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withWorkflowGroup(1, reviewer, admin).build(); + + ItemBuilder.createItem(context, col) + .withTitle("Punnett square") + .withIssueDate("2016-02-13") + .withAuthor("Bandola, Roman") + .withSubject("ExtraEntry").build(); + + // create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword(password).build(); + + context.setCurrentUser(submitter); + + PoolTaskBuilder.createPoolTask(context, col, reviewer) + .withTitle("Metaphysics") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry").build(); + + PoolTaskBuilder.createPoolTask(context, col, reviewer) + .withTitle("Mathematical Theory") + .withIssueDate("2020-01-19") + .withAuthor("Tommaso, Gattari") + .withSubject("ExtraEntry").build(); + + PoolTaskBuilder.createPoolTask(context, col, reviewer) + .withTitle("Test Metaphysics") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry").build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + getClient(adminToken).perform(get("/api/discover/search/objects") + .param("configuration", "workflow") + .param("sort", "dc.date.issued,DESC") + .param("query", "Mathematical Theory")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.query", is("Mathematical Theory"))) + .andExpect(jsonPath("$.configuration", is("workflow"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.contains( + SearchResultMatcher.match("workflow", "pooltask", "pooltasks") + ))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects",Matchers.contains( + allOf(hasJsonPath("$._embedded.indexableObject._embedded.workflowitem._embedded.item", + is(SearchResultMatcher.matchEmbeddedObjectOnItemName("item", "Mathematical Theory")))) + ))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + getClient(adminToken).perform(get("/api/discover/search/objects") + .param("configuration", "workflow") + .param("sort", "dc.date.issued,DESC") + .param("query", "Metaphysics")) + .andExpect(status().isOk()) .andExpect(jsonPath("$.query", is("Metaphysics"))) .andExpect(jsonPath("$.configuration", is("workflow"))) .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( @@ -6017,12 +6285,12 @@ public void discoverFacetsTestWithQueryTest() throws Exception { .andExpect(jsonPath("$.name", is("author"))) .andExpect(jsonPath("$.facetType", is("text"))) .andExpect(jsonPath("$.scope", is(emptyOrNullString()))) - .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets/author?query=Donald"))) .andExpect(jsonPath("$._embedded.values[0].label", is("Smith, Donald"))) .andExpect(jsonPath("$._embedded.values[0].count", is(1))) .andExpect(jsonPath("$._embedded.values[0]._links.search.href", - containsString("api/discover/search/objects?query=Donald&f.author=" + - urlPathSegmentEscaper().escape("Smith, Donald,equals") + containsString( + "api/discover/search/objects?query=Donald&configuration=defaultConfiguration&f.author=" + + urlPathSegmentEscaper().escape("Smith, Donald,equals") ))) .andExpect(jsonPath("$._embedded.values").value(Matchers.hasSize(1))); @@ -6072,253 +6340,18 @@ public void discoverFacetsTestWithDsoTypeTest() throws Exception { .andExpect(jsonPath("$.facetType", is("date"))) .andExpect(jsonPath("$.scope", is(emptyOrNullString()))) .andExpect(jsonPath("$._links.self.href", - containsString("api/discover/facets/dateIssued?dsoType=Item"))) + containsString( + "api/discover/facets/dateIssued?dsoType=Item&configuration=defaultConfiguration"))) .andExpect(jsonPath("$._embedded.values[0].label", is("2017 - 2020"))) .andExpect(jsonPath("$._embedded.values[0].count", is(3))) .andExpect(jsonPath("$._embedded.values[0]._links.search.href", - containsString("api/discover/search/objects?dsoType=Item&f.dateIssued=" + - urlPathSegmentEscaper().escape("[2017 TO 2020],equals") - ))) + containsString( + "api/discover/search/objects?dsoType=Item&configuration=defaultConfiguration" + + "&f.dateIssued=%5B2017%20TO%202020%5D,equals"))) .andExpect(jsonPath("$._embedded.values").value(Matchers.hasSize(1))); } - @Test - public void discoverFacetsSubjectTestWithCapitalAndSpecialChars() throws Exception { - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Parent Collection").build(); - - Item item1 = ItemBuilder.createItem(context, collection) - .withTitle("Item 1") - .withSubject("Value with: Multiple Words ") - .build(); - - Item item2 = ItemBuilder.createItem(context, collection) - .withTitle("Item 2") - .withSubject("Multiple worded subject ") - .build(); - - Item item3 = ItemBuilder.createItem(context, collection) - .withTitle("Item 3") - .withSubject("Subject with a lot of Word values") - .build(); - - Item item4 = ItemBuilder.createItem(context, collection) - .withTitle("Item 4") - .withSubject("With, Values") - .build(); - - Item item5 = ItemBuilder.createItem(context, collection) - .withTitle("Item 5") - .withSubject("Test:of:the:colon") - .build(); - - Item item6 = ItemBuilder.createItem(context, collection) - .withTitle("Item 6") - .withSubject("Test,of,comma") - .build(); - - Item item7 = ItemBuilder.createItem(context, collection) - .withTitle("Item 7") - .withSubject("N’guyen") - .build(); - - Item item8 = ItemBuilder.createItem(context, collection) - .withTitle("Item 8") - .withSubject("test;Semicolon") - .build(); - - Item item9 = ItemBuilder.createItem(context, collection) - .withTitle("Item 9") - .withSubject("test||of|Pipe") - .build(); - - Item item10 = ItemBuilder.createItem(context, collection) - .withTitle("Item 10") - .withSubject("Test-Subject") - .build(); - - context.restoreAuthSystemState(); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "with a lot of word")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "multiple words")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("Value with: Multiple Words", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "mUltiPle wor")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("Multiple worded subject", 1), - FacetValueMatcher.entrySubject("Value with: Multiple Words", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "with")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("With, Values", 1), - FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1), - FacetValueMatcher.entrySubject("Value with: Multiple Words", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "of")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1), - FacetValueMatcher.entrySubject("Test,of,comma", 1), - FacetValueMatcher.entrySubject("Test:of:the:colon", 1), - FacetValueMatcher.entrySubject("test||of|Pipe", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "tEsT")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("Test,of,comma", 1), - FacetValueMatcher.entrySubject("Test-Subject", 1), - FacetValueMatcher.entrySubject("Test:of:the:colon", 1), - FacetValueMatcher.entrySubject("test;Semicolon", 1), - FacetValueMatcher.entrySubject("test||of|Pipe", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "colon")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("Test:of:the:colon", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "coMma")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("Test,of,comma", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "guyen")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("N’guyen", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "semiColon")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("test;Semicolon", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "pipe")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("test||of|Pipe", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "Subject")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubject("Multiple worded subject", 1), - FacetValueMatcher.entrySubject("Test-Subject", 1), - FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "Subject of word")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values").isEmpty()) - .andExpect(jsonPath("$.page.number", is(0))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "Value with words")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values").isEmpty()) - .andExpect(jsonPath("$.page.number", is(0))); - } - - @Test - public void discoverFacetsSubjectWithAuthorityTest() throws Exception { - configurationService.setProperty("choices.plugin.dc.subject", "SolrSubjectAuthority"); - configurationService.setProperty("authority.controlled.dc.subject", "true"); - - metadataAuthorityService.clearCache(); - - context.turnOffAuthorisationSystem(); - - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community").build(); - - Collection collection = CollectionBuilder.createCollection(context, parentCommunity) - .withName("Parent Collection").build(); - - Item item1 = ItemBuilder.createItem(context, collection) - .withTitle("Item 1") - .withSubject("Value with: Multiple Words", - "test_authority_1", Choices.CF_ACCEPTED) - .build(); - - Item item2 = ItemBuilder.createItem(context, collection) - .withTitle("Item 2") - .withSubject("Multiple worded subject ", - "test_authority_2", Choices.CF_ACCEPTED) - .build(); - - Item item3 = ItemBuilder.createItem(context, collection) - .withTitle("Item 3") - .withSubject("Subject with a lot of Word values", - "test_authority_3", Choices.CF_ACCEPTED) - .build(); - - context.restoreAuthSystemState(); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "with a lot of word")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubjectWithAuthority("Subject with a lot of Word values", - "test_authority_3", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "mUltiPle wor")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubjectWithAuthority("Multiple worded subject", - "test_authority_2", 1), - FacetValueMatcher.entrySubjectWithAuthority("Value with: Multiple Words", - "test_authority_1", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "Subject")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entrySubjectWithAuthority("Multiple worded subject", - "test_authority_2", 1), - FacetValueMatcher.entrySubjectWithAuthority("Subject with a lot of Word values", - "test_authority_3", 1)))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "Subject of word")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values").isEmpty()) - .andExpect(jsonPath("$.page.number", is(0))); - - getClient().perform(get("/api/discover/facets/subject") - .param("prefix", "Value with words")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.values").isEmpty()) - .andExpect(jsonPath("$.page.number", is(0))); - - DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); - - metadataAuthorityService.clearCache(); - } - @Test public void discoverFacetsSupervisedByTest() throws Exception { //We turn off the authorization system in order to create the structure defined below @@ -6387,8 +6420,6 @@ public void discoverFacetsSupervisedByTest() throws Exception { .andExpect(jsonPath("$.type", is("discover"))) //The name has to be 'supervisedBy' as that's the facet that we've called .andExpect(jsonPath("$.name", is("supervisedBy"))) - //The facetType has to be `authority` because that's the default configuration for this facet - .andExpect(jsonPath("$.facetType", equalTo("authority"))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("api/discover/facets/supervisedBy?configuration=supervision"))) @@ -6461,7 +6492,7 @@ public void discoverFacetsSupervisedByWithPrefixTest() throws Exception { //The Admin user browses this endpoint to find the supervisedBy results by the facet getClient(getAuthToken(admin.getEmail(), password)).perform(get("/api/discover/facets/supervisedBy") .param("configuration", "supervision") - .param("prefix", "group B")) + .param("prefix", "group b")) //** THEN ** //The status has to be 200 OK .andExpect(status().isOk()) @@ -6469,11 +6500,9 @@ public void discoverFacetsSupervisedByWithPrefixTest() throws Exception { .andExpect(jsonPath("$.type", is("discover"))) //The name has to be 'supervisedBy' as that's the facet that we've called .andExpect(jsonPath("$.name", is("supervisedBy"))) - //The facetType has to be `authority` because that's the default configuration for this facet - .andExpect(jsonPath("$.facetType", equalTo("authority"))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", - containsString("api/discover/facets/supervisedBy?prefix=group%2520B&configuration=supervision"))) + containsString("api/discover/facets/supervisedBy?prefix=group%20b&configuration=supervision"))) //This is how the page object must look like because it's the default with size 20 .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 20)))) @@ -6805,4 +6834,783 @@ public void discoverSearchObjectsSupervisionConfigurationTest() throws Exception .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))); } + + + /** + * This test verifies a known bug fund with the DSC-940, + * the number of date facets returned by issuing a search with scope should be + * the same of a search issued using a filter for that entity scope. + * + * @throws Exception + */ + @Test + public void discoverFacetsTestSameResultWithOrWithoutScope() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = + CommunityBuilder.createCommunity(context) + .withName("SharedParentCommunity!!!") + .build(); + + Collection authors = + CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Person") + .withName("Authors") + .build(); + + Item author = + ItemBuilder.createItem(context, authors) + .withTitle("Author 1") + .withDspaceObjectOwner(admin) + .build(); + + Collection col1 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + Collection col2 = + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 2") + .build(); + + Item publicItem1 = + ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor(author.getName(), author.getID().toString()) + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = + ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2020-02-13") + .withAuthor(author.getName(), author.getID().toString()) + .withAuthor("Doe, Jane") + .withSubject("ExtraEntry") + .build(); + + Item publicItem3 = + ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2020-02-13") + .withAuthor(author.getName(), author.getID().toString()) + .withAuthor("Anton, Senek") + .withSubject("TestingForMore") + .withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + // finds the facets using the relation configuration using + // the author as scope + getClient() + .perform( + get("/api/discover/facets/dateIssued") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", author.getID().toString()) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$.name", is("dateIssued"))) + .andExpect(jsonPath("$.facetType", is("date"))) + .andExpect(jsonPath("$.scope", is(author.getID().toString()))) + .andExpect(jsonPath("$._links.self.href", + containsString( + "api/discover/facets/dateIssued?scope=" + + author.getID().toString() + + "&configuration=RELATION.Person.researchoutputs" + ) + )) + .andExpect(jsonPath("$._embedded.values[0].label", is("2017 - 2020"))) + .andExpect(jsonPath("$._embedded.values[0].count", is(3))) + .andExpect(jsonPath("$._embedded.values[0]._links.search.href", + containsString( + "api/discover/search/objects?scope=" + + author.getID().toString() + + "&configuration=RELATION.Person.researchoutputs" + + "&f.dateIssued=%5B2017%20TO%202020%5D,equals" + ) + )) + .andExpect(jsonPath("$._embedded.values").value(Matchers.hasSize(1))); + + // finds the facets using the default configuration and + // a filter that is the same used for the previous scope + getClient() + .perform( + get("/api/discover/facets/dateIssued") + .param("configuration", "defaultConfiguration") + .param("f.author", author.getID().toString() + ",authority") + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$.name", is("dateIssued"))) + .andExpect(jsonPath("$.facetType", is("date"))) + .andExpect(jsonPath("$.scope", is(emptyOrNullString()))) + .andExpect(jsonPath("$._links.self.href", + containsString( + "api/discover/facets/dateIssued?configuration=defaultConfiguration" + + "&f.author=" + author.getID().toString() + ",authority" + ) + )) + .andExpect(jsonPath("$._embedded.values[0].label", is("2017 - 2020"))) + .andExpect(jsonPath("$._embedded.values[0].count", is(3))) + .andExpect(jsonPath("$._embedded.values[0]._links.search.href", + containsString( + "api/discover/search/objects?configuration=defaultConfiguration" + + "&f.author=" + author.getID().toString() + ",authority" + + "&f.dateIssued=%5B2017%20TO%202020%5D,equals" + ) + )) + .andExpect(jsonPath("$._embedded.values").value(Matchers.hasSize(1))); + + } + + @Test + public void relevanceByRelationPlacesTest() throws Exception { + + configurationService.setProperty("relationship.places.onlyright", + "null::Person::isResearchoutputsSelectedFor::hasSelectedResearchoutputs"); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + final Collection publications = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Publication") + .build(); + final Collection patents = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Patent") + .build(); + final Collection people = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Person") + .build(); + + Item author1 = ItemBuilder.createItem(context, people) + .withTitle("Doe, John").build(); + Item author2 = ItemBuilder.createItem(context, people) + .withTitle("Smith, John").build(); + + Item publication1 = ItemBuilder.createItem(context, publications).withTitle("Publication 1") + .withAuthor(author1.getName(), author1.getID().toString()).build(); + Item publication2 = ItemBuilder.createItem(context, publications).withTitle("Publication 2") + .withAuthor(author1.getName(), author1.getID().toString()) + .withAuthor(author2.getName(), author2.getID().toString()) + .build(); + Item publication3 = ItemBuilder.createItem(context, publications).withTitle("Publication 3") + .withAuthor(author2.getName(), author2.getID().toString()) + .build(); + Item patent1 = ItemBuilder.createItem(context, patents).withTitle("Patent 1") + .withAuthor(author1.getName(), author1.getID().toString()) + .build(); + + final EntityType personEntity = Optional.ofNullable(entityTypeService.findByEntityType(context, "Person")) + .orElseGet(() -> EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build()); + final RelationshipType selectedResearchOutput = RelationshipTypeBuilder + .createRelationshipTypeBuilder( + context, + null, + personEntity, + "isResearchoutputsSelectedFor", + "hasSelectedResearchoutputs", + 0, null, + 0, null).build(); + + final Relationship publication2ToAuthor1 = + RelationshipBuilder.createRelationshipBuilder(context, publication2, author1, selectedResearchOutput, + -1, -1).build(); + + final Relationship publication1ToAuthor1 = + RelationshipBuilder.createRelationshipBuilder(context, publication1, + author1, selectedResearchOutput, -1, -1) + .build(); + final Relationship publication3ToAuthor2 = + RelationshipBuilder.createRelationshipBuilder(context, publication3, author2, selectedResearchOutput, + -1, -1) + .build(); + + final Relationship publication2ToAuthor2 = + RelationshipBuilder.createRelationshipBuilder(context, publication2, author2, selectedResearchOutput, + -1, -1) + .build(); + + final Relationship patent1ToAuthor1 = + RelationshipBuilder.createRelationshipBuilder(context, patent1, author1, selectedResearchOutput, + -1, -1) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", author1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.contains( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher.matchOnItemName("item", "items", "Publication 1"), + SearchResultMatcher.matchOnItemName("item", "items", "Patent 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(3))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", author2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.contains( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 3"), + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + configurationService.setProperty("relationship.places.onlyright", ""); + + + } + + @Test + public void hiddenItemsTest() throws Exception { + + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + final Collection publications = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Publication") + .build(); + final Collection projects = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Project") + .build(); + final Collection people = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Person") + .build(); + EPerson owner = EPersonBuilder.createEPerson(context) + .withEmail("test@test.com") + .withPassword("password") + .withCanLogin(true) + .withNameInMetadata("John", "Doe").build(); + + Item author = ItemBuilder.createItem(context, people) + .withDspaceObjectOwner(owner.getFullName(), UUIDUtils.toString(owner.getID())) + .withTitle("Doe, John").build(); + + Item publication1 = ItemBuilder.createItem(context, publications).withTitle("Publication 1") + .withAuthor(author.getName(), author.getID().toString()).build(); + Item publication2 = ItemBuilder.createItem(context, publications).withTitle("Publication 2") + .withAuthor(author.getName(), author.getID().toString()) + .build(); + + + Item project2 = ItemBuilder.createItem(context, projects).withTitle("Project 2") + .withProjectInvestigator(author.getName(), author.getID().toString()) + .build(); + + final EntityType personEntity = Optional.ofNullable(entityTypeService.findByEntityType(context, "Person")) + .orElseGet(() -> EntityTypeBuilder + .createEntityTypeBuilder( + context, "Person").build()); + final RelationshipType hiddenResearchOutput = RelationshipTypeBuilder + .createRelationshipTypeBuilder( + context, + null, + personEntity, + "isResearchoutputsHiddenFor", + "notDisplayingResearchoutputs", + 0, null, + 0, null).build(); + + final RelationshipType hiddenProject = RelationshipTypeBuilder + .createRelationshipTypeBuilder( + context, + null, + personEntity, + "isProjectsHiddenFor", + "notDisplayingProjects", + 0, null, + 0, null).build(); + + RelationshipBuilder.createRelationshipBuilder(context, publication1, author, hiddenResearchOutput) + .build(); + RelationshipBuilder.createRelationshipBuilder(context, project2, author, hiddenProject) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", author.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + + final String ownerToken = getAuthToken(owner.getEmail(), "password"); + + getClient(ownerToken).perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", author.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher.matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + + final String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", author.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + + } + + /** + * This test checks the scenario when an item is related to many owners and it is hidden by only one, many or all of + * them. + * @throws Exception + */ + @Test + public void sameItemHiddenByDifferentOwners() throws Exception { + + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + + + final Collection publications = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Publication") + .build(); + final Collection projects = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Project") + .build(); + final Collection people = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Person") + .build(); + + + Item firstPerson = ItemBuilder.createItem(context, people) + .withTitle("Doe, John").build(); + Item secondPerson = ItemBuilder.createItem(context, people) + .withTitle("Smith, Bryan").build(); + + Item project = ItemBuilder.createItem(context, projects).withTitle("Project 2") + .withProjectInvestigator(firstPerson.getName(), firstPerson.getID().toString()) + .build(); + + Item publication1 = ItemBuilder.createItem(context, publications).withTitle("Publication 1") + .withAuthor(firstPerson.getName(), firstPerson.getID().toString()) + .withAuthor(secondPerson.getName(), secondPerson.getID().toString()) + .withRelationProject(project.getName(), project.getID().toString()) + .build(); + + Item publication2 = ItemBuilder.createItem(context, publications).withTitle("Publication 2") + .withAuthor(firstPerson.getName(), firstPerson.getID().toString()) + .withAuthor(secondPerson.getName(), secondPerson.getID().toString()) + .withRelationProject(project.getName(), project.getID().toString()) + .build(); + + + + + final EntityType personEntity = Optional.ofNullable(entityTypeService.findByEntityType(context, "Person")) + .orElseGet(() -> EntityTypeBuilder + .createEntityTypeBuilder( + context, "Person").build()); + + final EntityType publicationEntity = Optional.ofNullable( + entityTypeService.findByEntityType(context, "Publication")) + .orElseGet(() -> EntityTypeBuilder + .createEntityTypeBuilder( + context, "Publication").build()); + + final EntityType projectEntity = Optional.ofNullable(entityTypeService.findByEntityType(context, "Project")) + .orElseGet(() -> EntityTypeBuilder + .createEntityTypeBuilder( + context, "Project").build()); + final RelationshipType researchOutputHiddenByPerson = RelationshipTypeBuilder + .createRelationshipTypeBuilder( + context, + null, + personEntity, + "isResearchoutputsHiddenFor", + "notDisplayingResearchoutputs", + 0, null, + 0, null).build(); + + final RelationshipType researchOutputHiddenByProject = RelationshipTypeBuilder + .createRelationshipTypeBuilder( + context, + null, + projectEntity, + "isResearchoutputsHiddenFor", + "notDisplayingResearchoutputs", + 0, null, + 0, null).build(); + + + // first scenario: publication 1 hidden by first person + final Relationship publicationOneHiddenByFirstPerson = + RelationshipBuilder.createRelationshipBuilder( + context, publication1, firstPerson, researchOutputHiddenByPerson) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", firstPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", secondPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Project.researchoutputs") + .param("scope", project.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Project.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + // second scenario: publication 2 hidden by second person + final String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(delete("/api/core/relationships/" + publicationOneHiddenByFirstPerson.getID())); + context.turnOffAuthorisationSystem(); + final Relationship publicationTwoHiddenBySecondPerson = + RelationshipBuilder.createRelationshipBuilder( + context, publication2, secondPerson, researchOutputHiddenByPerson) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", secondPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", firstPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Project.researchoutputs") + .param("scope", project.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Project.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + // third scenario: publication 1 hidden by project owner + getClient(adminToken).perform(delete("/api/core/relationships/" + publicationTwoHiddenBySecondPerson.getID())); + context.turnOffAuthorisationSystem(); + final Relationship publicationOneHiddenByProjectOwner = + RelationshipBuilder.createRelationshipBuilder(context, publication1, project, researchOutputHiddenByProject) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", firstPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", secondPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Project.researchoutputs") + .param("scope", project.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Project.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + // fifth scenario: publication 1 hidden by both authors and project owner + getClient(adminToken).perform(delete("/api/core/relationships/" + publicationOneHiddenByProjectOwner.getID())); + context.turnOffAuthorisationSystem(); + + RelationshipBuilder.createRelationshipBuilder(context, publication1, firstPerson, researchOutputHiddenByPerson) + .build(); + RelationshipBuilder.createRelationshipBuilder(context, publication1, secondPerson, researchOutputHiddenByPerson) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", firstPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", secondPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Project.researchoutputs") + .param("scope", project.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Project.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + // fifth scenario: publication 1 hidden by both authors and project owner + getClient(adminToken).perform(delete("/api/core/relationships/" + publicationOneHiddenByProjectOwner.getID())); + context.turnOffAuthorisationSystem(); + + RelationshipBuilder.createRelationshipBuilder(context, publication1, project, researchOutputHiddenByProject) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", firstPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", secondPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + getClient().perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Project.researchoutputs") + .param("scope", project.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Project.researchoutputs"))) + .andExpect(jsonPath("$._embedded.searchResult._embedded.objects", Matchers.containsInAnyOrder( + SearchResultMatcher.matchOnItemName("item", "items", "Publication 2")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(1))); + + + // admin user is able to see hidden items + + getClient(adminToken).perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", firstPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + getClient(adminToken).perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Person.researchoutputs") + .param("scope", secondPerson.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Person.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + getClient(adminToken).perform(get("/api/discover/search/objects") + .param("configuration", "RELATION.Project.researchoutputs") + .param("scope", project.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.configuration", is("RELATION.Project.researchoutputs"))) + .andExpect( + jsonPath("$._embedded.searchResult._embedded.objects", + Matchers.containsInAnyOrder( + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 2"), + SearchResultMatcher + .matchOnItemName("item", "items", "Publication 1")))) + .andExpect(jsonPath("$._embedded.searchResult.page.totalElements", is(2))); + + } + @Test + public void discoverChartWithDefaultConfigurationAndQueryWithSpaces() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community").build(); + + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community").build(); + + Collection col = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2").build(); + + Item publicItem2 = ItemBuilder.createItem(context, col) + .withTitle("Bollini, Andrea") + .withIssueDate("2016-02-13") + .withType("manuscript").build(); + + Item publicItem3 = ItemBuilder.createItem(context, col) + .withTitle("Bollini, Andrea") + .withIssueDate("2017-10-17") + .withAuthor("Boychuk, Michele") + .withSubject("AnotherTest") + .withType("manuscript").build(); + + Item publicItem4 = ItemBuilder.createItem(context, col) + .withTitle("Bollini, Andrea") + .withIssueDate("2020-02-13") + .withSubject("AnotherTest") + .withType("Journal Article") + .build(); + + context.restoreAuthSystemState(); + getClient().perform(get("/api/discover/facets/graphitemtype") + .param("size", "10") + .param("page", "0") + .param("query", "Bollini Andrea")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$.name", is("graphitemtype"))) + .andExpect(jsonPath("$.facetType", is("chart.pie"))) + .andExpect(jsonPath("$.facetLimit", is(10))) + .andExpect(jsonPath("$.query", is( "Bollini Andrea"))) + .andExpect(jsonPath("$._links.missing.href", + containsString("discover/facets/graphitemtype?query=Bollini%20Andrea" + + "&configuration=defaultConfiguration&f.graphitemtype=%5B*%20TO%20*%5D,notequals"))) + .andExpect(jsonPath("$._links.more.href", + Matchers.allOf(containsString("discover/facets/graphitemtype?query=Bollini%20Andrea" + + "&configuration=defaultConfiguration&f.graphitemtype=manuscript,notequals" + + "&f.graphitemtype=journal%20article,notequals")))) + .andExpect(jsonPath("$.totalElements", is("2"))) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 10)))) + .andExpect(jsonPath("$._embedded.values", contains( + FacetValueMatcher.entryDateIssuedWithLabelAndCount("manuscript", 2), + FacetValueMatcher.entryDateIssuedWithLabelAndCount("journal article", 1) + ))); + + + getClient().perform(get("/api/discover/facets/graphitemtype") + .param("size", "10") + .param("page", "0") + .param("query", "Bollini Andrea") + .param("f.graphitemtype", "journal article,equals")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.type", is("discover"))) + .andExpect(jsonPath("$.configuration", is("defaultConfiguration"))) + .andExpect(jsonPath("$.name", is("graphitemtype"))) + .andExpect(jsonPath("$.facetType", is("chart.pie"))) + .andExpect(jsonPath("$.facetLimit", is(10))) + .andExpect(jsonPath("$.query", is( "Bollini Andrea"))) + .andExpect(jsonPath("$._links.missing.href", + containsString("discover/facets/graphitemtype?query=Bollini%20Andrea" + + "&configuration=defaultConfiguration&f.graphitemtype=journal%20article,equals" + + "&f.graphitemtype=%5B*%20TO%20*%5D,notequals"))) + .andExpect(jsonPath("$._links.more.href", + containsString("discover/facets/graphitemtype?query=Bollini%20Andrea" + + "&configuration=defaultConfiguration&f.graphitemtype=journal%20article,equals" + + "&f.graphitemtype=journal%20article,notequals"))) + .andExpect(jsonPath("$.totalElements", is("1"))) + .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(0, 10)))) + .andExpect(jsonPath("$._embedded.values", contains( + FacetValueMatcher.entryDateIssuedWithLabelAndCount("journal article", 1) + ))); + } } diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index bde0b3f0184f..1ddf0da887e9 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -60,7 +60,9 @@ dc.contributor.author dc.contributor.editor - + + + @@ -94,6 +96,8 @@ + + @@ -988,6 +992,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + search.resourcetype:WorkspaceItem OR search.resourcetype:XmlWorkflowItem + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3223,6 +3302,18 @@ + + + + + + + + placeholder.placeholder.placeholder + + + From 914af1936bae9054c043256af6c54dab6cb0fcc6 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 17 May 2023 16:16:37 +0200 Subject: [PATCH 339/394] [DSC-1030] Fixed some tests --- .../discovery/SolrServiceValuePairsIndexPlugin.java | 3 +++ .../dspace/app/rest/EPersonGroupRestControllerIT.java | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceValuePairsIndexPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceValuePairsIndexPlugin.java index f6ccbabf0c6c..213d6547d958 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceValuePairsIndexPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceValuePairsIndexPlugin.java @@ -10,6 +10,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.dspace.discovery.SearchUtils.AUTHORITY_SEPARATOR; import static org.dspace.discovery.SearchUtils.FILTER_SEPARATOR; +import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES; import java.sql.SQLException; import java.util.List; @@ -127,10 +128,12 @@ private void addDiscoveryFieldFields(String language, SolrInputDocument document String keywordField = appendAuthorityIfNotBlank(value, authority); String acidField = appendAuthorityIfNotBlank(valueLowerCase + separator + value, authority); String filterField = appendAuthorityIfNotBlank(valueLowerCase + separator + value, authority); + String prefixField = appendAuthorityIfNotBlank(valueLowerCase + separator + value, authority); document.addField(fieldNameWithLanguage + "_keyword", keywordField); document.addField(fieldNameWithLanguage + "_acid", acidField); document.addField(fieldNameWithLanguage + "_filter", filterField); + document.addField(fieldNameWithLanguage + SOLR_FIELD_SUFFIX_FACET_PREFIXES, prefixField); document.addField(fieldNameWithLanguage + "_ac", valueLowerCase + separator + value); if (document.containsKey(searchFilter.getIndexFieldName() + "_authority")) { document.addField(fieldNameWithLanguage + "_authority", authority); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonGroupRestControllerIT.java index 5c19286a0419..fd23fd21c335 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonGroupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonGroupRestControllerIT.java @@ -6,6 +6,9 @@ * http://www.dspace.org/license/ */ package org.dspace.app.rest; + +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_QUERY_PARAM; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_REGISTER; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertNotNull; @@ -53,6 +56,7 @@ public void postGroupsForEpersonUsingRegistrationDataMultipleGroups() throws Exc registrationRest.setGroups(groupList); String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -66,6 +70,7 @@ public void postGroupsForEpersonUsingRegistrationDataMultipleGroups() throws Exc try { getClient(token).perform(post("/api/eperson/epersons/" + alreadyRegisteredPerson.getID() + "/groups") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.uuid", is((alreadyRegisteredPerson.getID().toString())))) @@ -105,6 +110,7 @@ public void postGroupsForEpersonUsingRegistrationDataOneGroup() throws Exception String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); EPerson alreadyRegisteredPerson = EPersonBuilder.createEPerson(context) @@ -117,6 +123,7 @@ public void postGroupsForEpersonUsingRegistrationDataOneGroup() throws Exception try { getClient(token).perform(post("/api/eperson/epersons/" + alreadyRegisteredPerson.getID() + "/groups") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.uuid", is((alreadyRegisteredPerson.getID().toString())))) @@ -155,6 +162,7 @@ public void postGroupsForEpersonDifferentFromLoggedInUsingRegistration() throws String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); EPerson alreadyRegisteredPerson = EPersonBuilder.createEPerson(context) @@ -167,6 +175,7 @@ public void postGroupsForEpersonDifferentFromLoggedInUsingRegistration() throws try { getClient(token).perform(post("/api/eperson/epersons/" + alreadyRegisteredPerson.getID() + "/groups") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); assertNotNull(registrationDataService.findByToken(context, newRegisterToken)); @@ -199,6 +208,7 @@ public void postGroupsForEpersonWithFakeTokenRegistration() throws Exception { try { getClient(token).perform(post("/api/eperson/epersons/" + alreadyRegisteredPerson.getID() + "/groups") .param("token", "justToBeTested") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()); } finally { @@ -216,6 +226,7 @@ public void postGroupsForEpersonUnregistered() throws Exception { registrationRest.setEmail(newRegisterEmail); String adminToken = getAuthToken(admin.getEmail(), password); getClient(adminToken).perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); From e205b40e6aa625b98abc571351646b917a03fcc8 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 17 May 2023 17:45:17 +0200 Subject: [PATCH 340/394] [DSC-1030] Fixed many tests --- .../rest/repository/RegistrationRestRepository.java | 11 +++++++++++ .../repository/VocabularyEntryLinkRepository.java | 2 +- .../app/rest/EPersonRestRepositoryInviationIT.java | 9 +++++++++ .../app/rest/SubmissionDefinitionsControllerIT.java | 2 +- .../app/rest/SupervisionOrderRestRepositoryIT.java | 2 ++ .../org/dspace/app/rest/iiif/IIIFControllerIT.java | 4 ++-- 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java index 76e21d65658b..3fbd6c9d9163 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RegistrationRestRepository.java @@ -128,6 +128,17 @@ public RegistrationRest createAndReturn(Context context) { if (StringUtils.isBlank(registrationRest.getEmail())) { throw new UnprocessableEntityException("The email cannot be omitted from the Registration endpoint"); } + if (Objects.nonNull(registrationRest.getGroups()) && registrationRest.getGroups().size() > 0) { + try { + if (Objects.isNull(context.getCurrentUser()) + || (!authorizeService.isAdmin(context) + && !hasPermission(context, registrationRest.getGroups()))) { + throw new AccessDeniedException("Only admin users can invite new users to join groups"); + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + } String accountType = request.getParameter(TYPE_QUERY_PARAM); if (StringUtils.isBlank(accountType) || (!accountType.equalsIgnoreCase(TYPE_FORGOT) && !accountType.equalsIgnoreCase(TYPE_REGISTER))) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java index edfdd1bf7d4b..dd0f03ca3088 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VocabularyEntryLinkRepository.java @@ -47,7 +47,7 @@ public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository @Autowired private AuthorityUtils authorityUtils; - @PreAuthorize("permitAll()") + @PreAuthorize("@vocabularySecurity.isVocabularyPublic(#name) || hasAuthority('AUTHENTICATED')") public Page filter(@Nullable HttpServletRequest request, String name, @Nullable Pageable optionalPageable, Projection projection) { Context context = obtainContext(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryInviationIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryInviationIT.java index 92f11549f5b1..534c3b2e7e11 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryInviationIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryInviationIT.java @@ -9,6 +9,8 @@ import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_QUERY_PARAM; +import static org.dspace.app.rest.repository.RegistrationRestRepository.TYPE_REGISTER; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -81,6 +83,7 @@ public void adminInvitedEPersonToGroupsTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(post("/api/eperson/registrations") + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -105,6 +108,7 @@ public void adminInvitedEPersonToGroupsTest() throws Exception { try { getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) @@ -160,6 +164,7 @@ public void simpleUserInvitedEPersonToGroupsTest() throws Exception { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isForbidden()); @@ -189,6 +194,7 @@ public void anonymousUserInvitedEPersonToGroupsTest() throws Exception { String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isForbidden()); @@ -217,6 +223,7 @@ public void adminInvitedEPersonToGroupNotExistTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isUnprocessableEntity()); @@ -246,6 +253,7 @@ public void removeOneGroupTest() throws Exception { String tokenAdmin = getAuthToken(admin.getEmail(), password); getClient(tokenAdmin).perform(post("/api/eperson/registrations") .contentType(MediaType.APPLICATION_JSON) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(registrationRest))) .andExpect(status().isCreated()); @@ -271,6 +279,7 @@ public void removeOneGroupTest() throws Exception { try { getClient().perform(post("/api/eperson/epersons") .param("token", newRegisterToken) + .param(TYPE_QUERY_PARAM, TYPE_REGISTER) .content(mapper.writeValueAsBytes(ePersonRest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index 5a467ce3a3ce..d3a830a5eb5b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -218,7 +218,7 @@ public void findSections() throws Exception { // We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) // Match only that a section exists with a submission configuration behind - .andExpect(jsonPath("$._embedded.submissionsections", hasSize(9))) + .andExpect(jsonPath("$._embedded.submissionsections", hasSize(10))) .andExpect(jsonPath("$._embedded.submissionsections", Matchers.hasItem( allOf( diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java index 27b436b6bc76..fbc707d6a491 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SupervisionOrderRestRepositoryIT.java @@ -714,6 +714,7 @@ public void createByAdminTest() throws Exception { CollectionBuilder.createCollection(context, parentCommunity) .withName("Publications") .withEntityType("Publication") + .withSubmissionDefinition("traditional") .build(); InputStream pdf = getClass().getResourceAsStream("simple-article.pdf"); @@ -1427,6 +1428,7 @@ public void supervisionOrderAddedToWorkflowItemThenSentBackToWorkspace() throws context.turnOffAuthorisationSystem(); Collection collection = CollectionBuilder.createCollection(context, parentCommunity) .withEntityType("Publication") + .withSubmissionDefinition("traditional") .withWorkflowGroup("reviewer", admin).build(); WorkflowItem workflowItem = WorkflowItemBuilder.createWorkflowItem(context, collection) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java index 01183aa44aa1..9e345a4b607e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/iiif/IIIFControllerIT.java @@ -139,8 +139,8 @@ public void findOneIIIFSearchableItemWithDefaultDimensionsIT() throws Exception .andExpect(jsonPath("$.metadata[2].value[0]", is("Smith, Donald"))) .andExpect(jsonPath("$.metadata[2].value[1]", is("Doe, John"))) .andExpect(jsonPath("$.sequences[0].canvases[0].@id", - Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) - .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) + Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas"))) + .andExpect(jsonPath("$.sequences[0].canvases[0].label", is("1"))) .andExpect(jsonPath("$.sequences[0].canvases[0].width", is(64))) .andExpect(jsonPath("$.sequences[0].canvases[0].height", is(64))) .andExpect(jsonPath("$.sequences[0].canvases[0].images[0].resource.service.@id", From cb4a80a85588418c4d555231593f03ed5887f3ac Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 18 May 2023 12:16:51 +0200 Subject: [PATCH 341/394] [DSC-1030] Fixed some tests --- .../WorkspaceItemRestPermissionEvaluatorPlugin.java | 8 ++++++++ .../test/java/org/dspace/app/rest/EPersonAuthorityIT.java | 2 +- .../dspace/app/rest/SubmissionShowIdentifiersRestIT.java | 1 + .../org/dspace/app/rest/VocabularyRestRepositoryIT.java | 8 ++++---- .../dspace/app/rest/WorkspaceItemRestRepositoryIT.java | 1 + 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java index c0efbd60f204..56633e80c025 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java @@ -18,6 +18,7 @@ import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.dspace.profile.service.ResearcherProfileService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; import org.dspace.supervision.service.SupervisionOrderService; @@ -43,6 +44,9 @@ public class WorkspaceItemRestPermissionEvaluatorPlugin extends RestObjectPermis @Autowired WorkspaceItemService wis; + @Autowired + private ResearcherProfileService researcherProfileService; + @Autowired private SupervisionOrderService supervisionOrderService; @@ -91,6 +95,10 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } + if (researcherProfileService.isAuthorOf(context, ePerson, witem.getItem())) { + return true; + } + if (witem.getItem() != null) { if (supervisionOrderService.isSupervisor(context, ePerson, witem.getItem())) { return authorizeService.authorizeActionBoolean(context, ePerson, witem.getItem(), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonAuthorityIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonAuthorityIT.java index d7921a2c86d8..3b785bbfa083 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonAuthorityIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonAuthorityIT.java @@ -119,7 +119,7 @@ public void testEPersonAuthorityWithAnonymousUser() throws Exception { getClient().perform(get("/api/submission/vocabularies/EPersonAuthority/entries") .param("filter", "Luca")) - .andExpect(status().isOk()); + .andExpect(status().isUnauthorized()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java index 8d95f4627b72..8d1912b29759 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionShowIdentifiersRestIT.java @@ -67,6 +67,7 @@ public void setUp() throws Exception { collection = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection") .withEntityType("Publication") + .withSubmissionDefinition("traditional") .withSubmitterGroup(submitter).build(); // Manually set configuration to allow registration handles, DOIs at workspace item creation diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java index 342d664c839f..1ff29a2ba228 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java @@ -566,7 +566,7 @@ public void controlledVocabularyWithHierarchyStoreSetTrueTest() throws Exception .andExpect(status().isOk()) .andExpect(jsonPath("$.metadata", Matchers.allOf( hasJsonPath("$['dc.title'][0].value", is("Test Item A")), - hasJsonPath("$['dc.type'][0].value", is("Resource Types::text::journal::editorial")), + hasJsonPath("$['dc.type'][0].value", is("text::journal::editorial")), hasJsonPath("$['dc.type'][0].authority", is(vocabularyName + ":c_b239")), hasJsonPath("$['dc.type'][0].confidence", is(600)) ))); @@ -656,7 +656,7 @@ public void controlledVocabularyWithHierarchySuggestSetTrueTest() throws Excepti hasJsonPath("$.authority", is(vocabularyName + ":c_b239")), // the display value without suggestions hasJsonPath("$.display", is("editorial")), - hasJsonPath("$.value", is("Resource Types::text::journal::editorial")) + hasJsonPath("$.value", is("text::journal::editorial")) ))); configurationService.setProperty("vocabulary.plugin." + vocabularyName + ".hierarchy.suggest", true); @@ -668,8 +668,8 @@ public void controlledVocabularyWithHierarchySuggestSetTrueTest() throws Excepti .andExpect(jsonPath("$._embedded.entries[0]", Matchers.allOf( hasJsonPath("$.authority", is(vocabularyName + ":c_b239")), // now the display value with suggestions - hasJsonPath("$.display", is("Resource Types::text::journal::editorial")), - hasJsonPath("$.value", is("Resource Types::text::journal::editorial")) + hasJsonPath("$.display", is("text::journal::editorial")), + hasJsonPath("$.value", is("text::journal::editorial")) ))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index e65efe366ab0..320fac1fd529 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -9820,6 +9820,7 @@ public void patchBySupervisorTest() throws Exception { CollectionBuilder.createCollection(context, parentCommunity) .withName("Publications") .withEntityType("Publication") + .withSubmissionDefinition("traditional") .build(); Group groupA = From f931124219daaf3c0fde00f700639c41c6bf1ded Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 18 May 2023 13:44:10 +0200 Subject: [PATCH 342/394] [DSC-1030] Finalized merge --- .../app/rest/BrowsesResourceControllerIT.java | 501 ------------------ 1 file changed, 501 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index e32d9e16f711..427235e1367a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -490,251 +490,6 @@ public void findBrowseBySubjectItems() throws Exception { .andExpect(jsonPath("$.page.size", is(20))); } - @Test - public void findBrowseBySubjectItemsWithScope() throws Exception { - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and two collections. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); - - //2. Two public items with the same subject and another public item that contains that same subject, but also - // another one - // All of the items are readable by an Anonymous user - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("zPublic item more") - .withIssueDate("2017-10-17") - .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withSubject("ExtraEntry").withSubject("AnotherTest") - .build(); - - Item publicItem2 = ItemBuilder.createItem(context, col2) - .withTitle("Public item 2") - .withIssueDate("2016-02-13") - .withAuthor("Smith, Maria").withAuthor("Doe, Jane") - .withSubject("AnotherTest") - .build(); - - Item publicItem3 = ItemBuilder.createItem(context, col2) - .withTitle("Public item 3") - .withIssueDate("2016-02-14") - .withAuthor("Smith, Maria").withAuthor("Doe, Jane") - .withSubject("AnotherTest") - .build(); - - Item withdrawnItem1 = ItemBuilder.createItem(context, col2) - .withTitle("Withdrawn item 1") - .withIssueDate("2016-02-13") - .withAuthor("Smith, Maria").withAuthor("Doe, Jane") - .withSubject("AnotherTest").withSubject("TestingForMore") - .withSubject("ExtraEntry").withSubject("WithdrawnEntry") - .withdrawn() - .build(); - Item privateItem1 = ItemBuilder.createItem(context, col2) - .withTitle("Private item 1") - .withIssueDate("2016-02-13") - .withAuthor("Smith, Maria").withAuthor("Doe, Jane") - .withSubject("AnotherTest").withSubject("TestingForMore") - .withSubject("ExtraEntry").withSubject("PrivateEntry") - .makeUnDiscoverable() - .build(); - - context.restoreAuthSystemState(); - - //** WHEN ** - //An anonymous user browses the items that correspond with the ExtraEntry subject query - getClient().perform(get("/api/discover/browses/subject/items") - .param("scope", String.valueOf(col2.getID())) - .param("filterValue", "ExtraEntry")) - //** THEN ** - //The status has to be 200 - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //We expect there to be no elements in collection 2 - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.size", is(20))); - - //** WHEN ** - //An anonymous user browses the items that correspond with the AnotherTest subject query - getClient().perform(get("/api/discover/browses/subject/items") - .param("scope", String.valueOf(col2.getID())) - .param("filterValue", "AnotherTest")) - //** THEN ** - //The status has to be 200 - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //We expect there to be only two elements, the ones that we've added with the requested subject - // in collection 2 - .andExpect(jsonPath("$.page.totalElements", is(2))) - .andExpect(jsonPath("$.page.size", is(20))) - //Verify that the title of the public and embargoed items are present and sorted descending - .andExpect(jsonPath("$._embedded.items", contains( - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, "Public item 2", "2016-02-13"), - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, "Public item 3", "2016-02-14") - ))); - - //** WHEN ** - //An anonymous user browses the items that correspond with the PrivateEntry subject query - getClient().perform(get("/api/discover/browses/subject/items") - .param("scope", String.valueOf(col2.getID())) - .param("filterValue", "PrivateEntry")) - //** THEN ** - //The status has to be 200 - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //We expect there to be no elements because the item is private - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.size", is(20))); - - //** WHEN ** - //An anonymous user browses the items that correspond with the WithdrawnEntry subject query - getClient().perform(get("/api/discover/browses/subject/items") - .param("scope", String.valueOf(col2.getID())) - .param("filterValue", "WithdrawnEntry")) - //** THEN ** - //The status has to be 200 - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //We expect there to be no elements because the item is withdrawn - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.size", is(20))); - } - - @Test - public void findBrowseBySubjectItemsWithScopeAsAdmin() throws Exception { - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and two collections. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); - - //2. Two public items with the same subject and another public item that contains that same subject, but also - // another one - // All of the items are readable by an Anonymous user - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("zPublic item more") - .withIssueDate("2017-10-17") - .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withSubject("ExtraEntry").withSubject("AnotherTest") - .build(); - - Item publicItem2 = ItemBuilder.createItem(context, col2) - .withTitle("Public item 2") - .withIssueDate("2016-02-13") - .withAuthor("Smith, Maria").withAuthor("Doe, Jane") - .withSubject("AnotherTest") - .build(); - - Item publicItem3 = ItemBuilder.createItem(context, col2) - .withTitle("Public item 3") - .withIssueDate("2016-02-14") - .withAuthor("Smith, Maria").withAuthor("Doe, Jane") - .withSubject("AnotherTest") - .build(); - - Item withdrawnItem1 = ItemBuilder.createItem(context, col2) - .withTitle("Withdrawn item 1") - .withIssueDate("2016-02-13") - .withAuthor("Smith, Maria").withAuthor("Doe, Jane") - .withSubject("AnotherTest").withSubject("TestingForMore") - .withSubject("ExtraEntry").withSubject("WithdrawnEntry") - .withdrawn() - .build(); - Item privateItem1 = ItemBuilder.createItem(context, col2) - .withTitle("Private item 1") - .withIssueDate("2016-02-13") - .withAuthor("Smith, Maria").withAuthor("Doe, Jane") - .withSubject("AnotherTest").withSubject("TestingForMore") - .withSubject("ExtraEntry").withSubject("PrivateEntry") - .makeUnDiscoverable() - .build(); - - context.restoreAuthSystemState(); - - String adminToken = getAuthToken(admin.getEmail(), password); - - - //** WHEN ** - //An admin user browses the items that correspond with the ExtraEntry subject query - getClient(adminToken).perform(get("/api/discover/browses/subject/items") - .param("scope", String.valueOf(col2.getID())) - .param("filterValue", "ExtraEntry")) - //** THEN ** - //The status has to be 200 - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //We expect there to be no elements in collection 2 - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.size", is(20))); - - //** WHEN ** - //An admin user browses the items that correspond with the AnotherTest subject query - getClient(adminToken).perform(get("/api/discover/browses/subject/items") - .param("scope", String.valueOf(col2.getID())) - .param("filterValue", "AnotherTest")) - //** THEN ** - //The status has to be 200 - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //We expect there to be only two elements, the ones that we've added with the requested subject - // in collection 2 - .andExpect(jsonPath("$.page.totalElements", is(2))) - .andExpect(jsonPath("$.page.size", is(20))) - //Verify that the title of the public and embargoed items are present and sorted descending - .andExpect(jsonPath("$._embedded.items", contains( - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, "Public item 2", "2016-02-13"), - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem3, "Public item 3", "2016-02-14") - ))); - - //** WHEN ** - //An admin user browses the items that correspond with the PrivateEntry subject query - getClient(adminToken).perform(get("/api/discover/browses/subject/items") - .param("scope", String.valueOf(col2.getID())) - .param("filterValue", "PrivateEntry")) - //** THEN ** - //The status has to be 200 - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //We expect there to be no elements because the item is private - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.size", is(20))); - - //** WHEN ** - //An admin user browses the items that correspond with the WithdrawnEntry subject query - getClient(adminToken).perform(get("/api/discover/browses/subject/items") - .param("scope", String.valueOf(col2.getID())) - .param("filterValue", "WithdrawnEntry")) - //** THEN ** - //The status has to be 200 - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - //We expect there to be no elements because the item is withdrawn - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$.page.size", is(20))); - } - @Test public void findBrowseByTitleItems() throws Exception { context.turnOffAuthorisationSystem(); @@ -872,135 +627,6 @@ public void findBrowseByTitleItems() throws Exception { not(matchMetadata("dc.title", "Internal publication"))))); } - @Test - public void findBrowseByTitleItemsWithScope() throws Exception { - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and two collections. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); - - //2. Two public items that are readable by Anonymous - Item publicItem1 = ItemBuilder.createItem(context, col1) - .withTitle("Public item 1") - .withIssueDate("2017-10-17") - .withAuthor("Smith, Donald").withAuthor("Doe, John") - .withSubject("Java").withSubject("Unit Testing") - .build(); - - Item publicItem2 = ItemBuilder.createItem(context, col2) - .withTitle("Public item 2") - .withIssueDate("2016-02-13") - .withAuthor("Smith, Maria").withAuthor("Doe, Jane") - .withSubject("Angular").withSubject("Unit Testing") - .build(); - - //3. An item that has been made private - Item privateItem = ItemBuilder.createItem(context, col2) - .withTitle("This is a private item") - .withIssueDate("2015-03-12") - .withAuthor("Duck, Donald") - .withSubject("Cartoons").withSubject("Ducks") - .makeUnDiscoverable() - .build(); - - //4. An item with an item-level embargo - Item embargoedItem = ItemBuilder.createItem(context, col2) - .withTitle("An embargoed publication") - .withIssueDate("2017-08-10") - .withAuthor("Mouse, Mickey") - .withSubject("Cartoons").withSubject("Mice") - .withEmbargoPeriod("12 months") - .build(); - - //5. An item that is only readable for an internal groups - Group internalGroup = GroupBuilder.createGroup(context) - .withName("Internal Group") - .build(); - - Item internalItem = ItemBuilder.createItem(context, col2) - .withTitle("Internal publication") - .withIssueDate("2016-09-19") - .withAuthor("Doe, John") - .withSubject("Unknown") - .withReaderGroup(internalGroup) - .build(); - - context.restoreAuthSystemState(); - - //** WHEN ** - //An anonymous user browses the items in the Browse by item endpoint - //sorted descending by tile - getClient().perform(get("/api/discover/browses/title/items") - .param("scope", String.valueOf(col2.getID())) - .param("sort", "title,desc")) - - //** THEN ** - //The status has to be 200 OK - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", is(1))) - .andExpect(jsonPath("$.page.totalPages", is(1))) - .andExpect(jsonPath("$.page.number", is(0))) - - .andExpect(jsonPath("$._embedded.items", - contains(ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, - "Public item 2", - "2016-02-13")))) - - //The private and internal items must not be present - .andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf( - not(matchMetadata("dc.title", "This is a private item")), - not(matchMetadata("dc.title", "Internal publication"))))); - - String adminToken = getAuthToken(admin.getEmail(), password); - //** WHEN ** - //An admin user browses the items in the Browse by item endpoint - //sorted descending by tile - getClient(adminToken).perform(get("/api/discover/browses/title/items") - .param("scope", String.valueOf(col2.getID())) - .param("sort", "title,desc")) - - //** THEN ** - //The status has to be 200 OK - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(1))) - .andExpect(jsonPath("$.page.number", is(0))) - .andExpect(jsonPath("$._embedded.items", contains( - ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, - "Public item 2", - "2016-02-13"), - ItemMatcher.matchItemWithTitleAndDateIssued(internalItem, - "Internal publication", - "2016-09-19"), - ItemMatcher.matchItemWithTitleAndDateIssued(embargoedItem, - "An embargoed publication", - "2017-08-10") - - ))) - - - //The private and internal items must not be present - .andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf( - not(matchMetadata("dc.title", "This is a private item")) - ))); - } - @Test /** * This test was introduced to reproduce the bug DS-4269 Pagination links must be consistent also when there is not @@ -1226,133 +852,6 @@ public void testPaginationBrowseByDateIssuedItems() throws Exception { ))); } - @Test - public void testPaginationBrowseByDateIssuedItemsWithScope() throws Exception { - context.turnOffAuthorisationSystem(); - - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and two collections. - parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); - Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) - .withName("Sub Community") - .build(); - Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); - Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); - - //2. 7 public items that are readable by Anonymous - Item item1 = ItemBuilder.createItem(context, col1) - .withTitle("Item 1") - .withIssueDate("2017-10-17") - .build(); - - Item item2 = ItemBuilder.createItem(context, col2) - .withTitle("Item 2") - .withIssueDate("2016-02-13") - .build(); - - Item item3 = ItemBuilder.createItem(context, col1) - .withTitle("Item 3") - .withIssueDate("2016-02-12") - .build(); - - Item item4 = ItemBuilder.createItem(context, col2) - .withTitle("Item 4") - .withIssueDate("2016-02-11") - .build(); - - Item item5 = ItemBuilder.createItem(context, col1) - .withTitle("Item 5") - .withIssueDate("2016-02-10") - .build(); - - Item item6 = ItemBuilder.createItem(context, col2) - .withTitle("Item 6") - .withIssueDate("2016-01-13") - .build(); - - Item item7 = ItemBuilder.createItem(context, col1) - .withTitle("Item 7") - .withIssueDate("2016-01-12") - .build(); - - Item withdrawnItem1 = ItemBuilder.createItem(context, col2) - .withTitle("Withdrawn item 1") - .withIssueDate("2016-02-13") - .withdrawn() - .build(); - - Item privateItem1 = ItemBuilder.createItem(context, col2) - .withTitle("Private item 1") - .makeUnDiscoverable() - .build(); - - - context.restoreAuthSystemState(); - - //** WHEN ** - //An anonymous user browses the items in the Browse by date issued endpoint - //sorted ascending by tile with a page size of 5 - getClient().perform(get("/api/discover/browses/dateissued/items") - .param("scope", String.valueOf(col2.getID())) - .param("sort", "title,asc") - .param("size", "5")) - - //** THEN ** - //The status has to be 200 OK - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - - //We expect only the first five items to be present - .andExpect(jsonPath("$.page.size", is(5))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(1))) - .andExpect(jsonPath("$.page.number", is(0))) - - //Verify that the title and date of the items match and that they are sorted ascending - .andExpect(jsonPath("$._embedded.items", - contains( - ItemMatcher.matchItemWithTitleAndDateIssued(item2, - "Item 2", "2016-02-13"), - ItemMatcher.matchItemWithTitleAndDateIssued(item4, - "Item 4", "2016-02-11"), - ItemMatcher.matchItemWithTitleAndDateIssued(item6, - "Item 6", "2016-01-13") - ))); - - String adminToken = getAuthToken(admin.getEmail(), password); - getClient(adminToken).perform(get("/api/discover/browses/dateissued/items") - .param("scope", String.valueOf(col2.getID())) - .param("sort", "title,asc") - .param("size", "5")) - - //** THEN ** - //The status has to be 200 OK - .andExpect(status().isOk()) - //We expect the content type to be "application/hal+json;charset=UTF-8" - .andExpect(content().contentType(contentType)) - - //We expect only the first five items to be present - .andExpect(jsonPath("$.page.size", is(5))) - .andExpect(jsonPath("$.page.totalElements", is(3))) - .andExpect(jsonPath("$.page.totalPages", is(1))) - .andExpect(jsonPath("$.page.number", is(0))) - - //Verify that the title and date of the items match and that they are sorted ascending - .andExpect(jsonPath("$._embedded.items", - contains( - ItemMatcher.matchItemWithTitleAndDateIssued(item2, - "Item 2", "2016-02-13"), - ItemMatcher.matchItemWithTitleAndDateIssued(item4, - "Item 4", "2016-02-11"), - ItemMatcher.matchItemWithTitleAndDateIssued(item6, - "Item 6", "2016-01-13") - ))); - - } - @Test public void testBrowseByEntriesStartsWith() throws Exception { context.turnOffAuthorisationSystem(); From fc20caf4d7befdfe72b4e90e0bbf5c1d4a76278d Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 18 May 2023 15:26:47 +0200 Subject: [PATCH 343/394] [DSC-1030] Remove duplicated bean --- dspace/config/spring/api/scripts.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index d3f4e4cd17c4..dc598764573b 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -109,11 +109,7 @@ - - - - - + From 471cbf57883f4331f81fb98898492761d02dcc9a Mon Sep 17 00:00:00 2001 From: Oliver Goldschmidt Date: Fri, 19 May 2023 12:51:23 +0200 Subject: [PATCH 344/394] adds subject to subscription emails --- dspace/config/emails/subscriptions_content | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace/config/emails/subscriptions_content b/dspace/config/emails/subscriptions_content index 1602807c029b..1be7a8775a55 100644 --- a/dspace/config/emails/subscriptions_content +++ b/dspace/config/emails/subscriptions_content @@ -3,6 +3,7 @@ ## Parameters: {0} Collections updates ## {1} Communities updates ## {2} Items updates +#set($subject = 'DSpace: Your Content Subscriptions') This email is sent from DSpace-CRIS based on the chosen subscription preferences. From f22bf9d3075c0a10bfdc287713032cf60c949d32 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 May 2023 13:16:24 +0200 Subject: [PATCH 345/394] [DSC-1092] Improvment of Patent Live Import --- .../EpoImportMetadataSourceServiceImpl.java | 16 ++-- .../spring-dspace-addon-import-services.xml | 2 +- dspace/config/registries/crispatent-types.xml | 46 ++++++++++ dspace/config/spring/api/epo-integration.xml | 84 +++++++++++++++---- dspace/config/submission-forms.xml | 83 ++++++++++++++++-- 5 files changed, 202 insertions(+), 29 deletions(-) create mode 100644 dspace/config/registries/crispatent-types.xml diff --git a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java index 395d6b48c987..f5d5717b6eec 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java @@ -59,6 +59,7 @@ * Implements a data source for querying EPO * * @author Pasquale Cavallo (pasquale.cavallo at 4Science dot it) + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4Science.com) */ public class EpoImportMetadataSourceServiceImpl extends AbstractImportMetadataSourceService implements QuerySource { @@ -303,7 +304,13 @@ private SearchByIdCallable(String id, String bearer) { } public List call() throws Exception { - if (id.contains(APP_NO_DATE_SEPARATOR)) { + int positionToSplit = id.indexOf(":"); + String docType = EpoDocumentId.EPODOC; + String idS = id; + if (positionToSplit != -1) { + docType = id.substring(0, positionToSplit); + idS = id.substring(positionToSplit + 1, id.length()); + } else if (id.contains(APP_NO_DATE_SEPARATOR)) { // special case the id is the combination of the applicationnumber and date filed String query = "applicationnumber=" + id.split(APP_NO_DATE_SEPARATOR_REGEX)[0]; SearchByQueryCallable search = new SearchByQueryCallable(query, bearer, 0, 10); @@ -316,12 +323,7 @@ public List call() throws Exception { return records; } // search by Patent Number - String[] identifier = id.split(":"); - String patentIdentifier = identifier.length == 2 ? identifier[1] : id; - List records = retry(new SearchByQueryCallable(patentIdentifier, bearer, null, null)); - if (records.size() > 1) { - log.warn("More record are returned with Patent Number: " + id); - } + List records = searchDocument(bearer, idS, docType); return records; } } diff --git a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml index b095470c7c56..1345d4ed3007 100644 --- a/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml +++ b/dspace-api/src/main/resources/spring/spring-dspace-addon-import-services.xml @@ -136,7 +136,7 @@ - + diff --git a/dspace/config/registries/crispatent-types.xml b/dspace/config/registries/crispatent-types.xml new file mode 100644 index 000000000000..cd6138d17036 --- /dev/null +++ b/dspace/config/registries/crispatent-types.xml @@ -0,0 +1,46 @@ + + + + DSpace Cris Patent Types + + + + crispatent + http://dspace.org/crispatent + + + + crispatent + kind + + + + + crispatent + document + kind + + + + + crispatent + document + issueDate + + + + + crispatent + document + title + + + + + crispatent + document + description + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/epo-integration.xml b/dspace/config/spring/api/epo-integration.xml index 57f7ce9919c3..a4b6a5b31391 100644 --- a/dspace/config/spring/api/epo-integration.xml +++ b/dspace/config/spring/api/epo-integration.xml @@ -19,19 +19,75 @@
+ - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -40,7 +96,7 @@ - + @@ -49,7 +105,7 @@ - + @@ -59,7 +115,7 @@ - + @@ -68,7 +124,7 @@ - + @@ -77,7 +133,7 @@ - + @@ -86,7 +142,7 @@ - + @@ -95,7 +151,7 @@ - + @@ -104,7 +160,7 @@ - + @@ -114,13 +170,13 @@ - - + + - + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index 511b4c3e033c..bc192f9551a4 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -168,6 +168,54 @@ +
+ + + crispatent + document + kind + + onebox + false + You must enter the kind code. + + + + + crispatent + document + issueDate + + onebox + false + You must enter the publication date. + + + + + crispatent + document + title + + onebox + false + + + + + + crispatent + document + description + + textarea + false + + + + + +
@@ -1381,6 +1429,15 @@ + + crispatent + kind + + onebox + false + + The kind code + dc identifier @@ -1392,9 +1449,10 @@ The application number - dcterms - dateSubmitted - + dc + date + issued + date false @@ -1425,10 +1483,9 @@ - dc - date - issued - + dcterms + dateSubmitted + date false @@ -1481,6 +1538,18 @@ patent-coar-types + + + crispatent + document + kind + + inline-group + true + + + +
From 7b1d3d1a4201f499e34cee3476a374dfc9948524 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 May 2023 23:23:32 +0200 Subject: [PATCH 346/394] [DSC-1092] improvment of EPO --- ...atumAndAttributeAndSubNodeContributor.java | 87 +++++++++++++++++++ ...XpathMetadatumAndAttributeContributor.java | 2 +- dspace/config/spring/api/epo-integration.xml | 19 ++-- 3 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeAndSubNodeContributor.java diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeAndSubNodeContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeAndSubNodeContributor.java new file mode 100644 index 000000000000..aae07b1ff263 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeAndSubNodeContributor.java @@ -0,0 +1,87 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.metadatamapping.contributor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.filter.Filters; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; + +/** + * Metadata contributor that takes multiple value of the some nome. + * Can fileter also nedes by attribute element value. + * + * @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com) + */ +public class SimpleXpathMetadatumAndAttributeAndSubNodeContributor extends SimpleXpathMetadatumAndAttributeContributor { + + private String attributeValue; + private String queryToSubNode; + + @Override + public Collection contributeMetadata(Element t) { + List values = new LinkedList<>(); + List namespaces = new ArrayList(); + for (String ns : prefixToNamespaceMapping.keySet()) { + namespaces.add(Namespace.getNamespace(prefixToNamespaceMapping.get(ns), ns)); + } + + List nodes = getNodes(t, query, namespaces); + List subNodes = getSubNodes(namespaces, nodes); + for (Object el : subNodes) { + if (el instanceof Element) { + values.add(metadataFieldMapping.toDCValue(this.field, extractValue(el))); + } + } + return values; + } + + private List getSubNodes(List namespaces, List nodes) { + List allNodes = new ArrayList(); + for (Object el : nodes) { + if (el instanceof Element) { + List elements = ((Element) el).getChildren(); + for (Element element : elements) { + String attributeValue = element.getAttributeValue(this.attribute); + if (StringUtils.equals(attributeValue, this.attributeValue)) { + List subNodes = getNodes(element, queryToSubNode, namespaces); + allNodes.addAll(subNodes); + } + } + } + } + return allNodes; + } + + private List getNodes(Element t, String query, List namespaces) { + XPathExpression xpath = XPathFactory.instance().compile(query, Filters.fpassthrough(),null, namespaces); + return xpath.evaluate(t); + } + + private String extractValue(Object el) { + String value = ((Element) el).getText(); + return StringUtils.isNotBlank(value) ? value : ((Element) el).getValue().trim(); + } + + public void setAttributeValue(String attributeValue) { + this.attributeValue = attributeValue; + } + + public void setQueryToSubNode(String queryToSubNode) { + this.queryToSubNode = queryToSubNode; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java index dea840d15b38..1fd9d168338d 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SimpleXpathMetadatumAndAttributeContributor.java @@ -33,7 +33,7 @@ public class SimpleXpathMetadatumAndAttributeContributor extends SimpleXpathMeta private final static Logger log = LogManager.getLogger(); - private String attribute; + protected String attribute; @Override public Collection contributeMetadata(Element t) { diff --git a/dspace/config/spring/api/epo-integration.xml b/dspace/config/spring/api/epo-integration.xml index a4b6a5b31391..4836fba7d4de 100644 --- a/dspace/config/spring/api/epo-integration.xml +++ b/dspace/config/spring/api/epo-integration.xml @@ -85,9 +85,10 @@ - + - + + @@ -122,9 +123,12 @@ - + - + + + + @@ -140,9 +144,12 @@ - + - + + + + From 79b5a413e8a3b1e45d449fbf6d71222fbb62e864 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 23 May 2023 23:23:49 +0200 Subject: [PATCH 347/394] [dsc-1092] fix tests --- .../EpoImportMetadataSourceServiceIT.java | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EpoImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EpoImportMetadataSourceServiceIT.java index d86dd1875d8d..f6b944d34264 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EpoImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EpoImportMetadataSourceServiceIT.java @@ -148,28 +148,35 @@ private ArrayList getRecords() { List metadatums = new ArrayList(); MetadatumDTO identifierOther = createMetadatumDTO("dc", "identifier", "other", "epodoc:ES2902749T"); MetadatumDTO patentno = createMetadatumDTO("dc", "identifier", "patentno", "ES2902749T"); + MetadatumDTO kind = createMetadatumDTO("crispatent", "kind", null, "T3"); MetadatumDTO identifier = createMetadatumDTO("dc", "identifier", "applicationnumber", "18705153"); MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2022-03-29"); MetadatumDTO dateSubmitted = createMetadatumDTO("dcterms", "dateSubmitted", null, "2018-02-19"); - MetadatumDTO applicant = createMetadatumDTO("dc", "contributor", null, "PANKA BLOOD TEST GMBH"); - MetadatumDTO applicant2 = createMetadatumDTO("dc", "contributor", null, "Panka Blood Test GmbH"); + MetadatumDTO applicant = createMetadatumDTO("dc", "contributor", null, "Panka Blood Test GmbH"); MetadatumDTO author = createMetadatumDTO("dc", "contributor", "author", "PANTEL, Klaus, "); MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "BARTKOWIAK, Kai"); MetadatumDTO title = createMetadatumDTO("dc", "title", null, "Método para el diagnóstico del cáncer de mama"); MetadatumDTO subject = createMetadatumDTO("dc", "subject", null, "G01N 33/ 574 A I "); + MetadatumDTO kindCodeInline = createMetadatumDTO("crispatent", "document", "kind", "T3"); + MetadatumDTO issueDateInline = createMetadatumDTO("crispatent", "document", "issueDate", "2022-03-29"); + MetadatumDTO titleInline = createMetadatumDTO("crispatent", "document", "title", + "Método para el diagnóstico del cáncer de mama"); metadatums.add(identifierOther); metadatums.add(patentno); + metadatums.add(kind); metadatums.add(identifier); metadatums.add(date); metadatums.add(dateSubmitted); metadatums.add(applicant); - metadatums.add(applicant2); metadatums.add(author); metadatums.add(author2); metadatums.add(title); metadatums.add(subject); + metadatums.add(kindCodeInline); + metadatums.add(issueDateInline); + metadatums.add(titleInline); ImportRecord firstrRecord = new ImportRecord(metadatums); @@ -177,11 +184,11 @@ private ArrayList getRecords() { List metadatums2 = new ArrayList(); MetadatumDTO identifierOther2 = createMetadatumDTO("dc", "identifier", "other", "epodoc:TW202202864"); MetadatumDTO patentno2 = createMetadatumDTO("dc", "identifier", "patentno", "TW202202864"); + MetadatumDTO kind2 = createMetadatumDTO("crispatent", "kind", null, "A"); MetadatumDTO identifier2 = createMetadatumDTO("dc", "identifier", "applicationnumber", "109122801"); MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2022-01-16"); MetadatumDTO dateSubmitted2 = createMetadatumDTO("dcterms", "dateSubmitted", null, "2020-07-06"); - MetadatumDTO applicant3 = createMetadatumDTO("dc", "contributor", null, "ADVANTEST CORP [JP]"); - MetadatumDTO applicant4 = createMetadatumDTO("dc", "contributor", null, "ADVANTEST CORPORATION"); + MetadatumDTO applicant2 = createMetadatumDTO("dc", "contributor", null, "ADVANTEST CORPORATION"); MetadatumDTO author5 = createMetadatumDTO("dc", "contributor", "author", "POEPPE, OLAF, "); MetadatumDTO author6 = createMetadatumDTO("dc", "contributor", "author", "HILLIGES, KLAUS-DIETER, "); MetadatumDTO author7 = createMetadatumDTO("dc", "contributor", "author", "KRECH, ALAN"); @@ -192,19 +199,29 @@ private ArrayList getRecords() { "G01R 31/ 319 A I "); MetadatumDTO subject3 = createMetadatumDTO("dc", "subject", null, "G01R 31/ 3193 A I "); + MetadatumDTO kindCodeInline2 = createMetadatumDTO("crispatent", "document", "kind", "A"); + MetadatumDTO issueDateInline2 = createMetadatumDTO("crispatent", "document", "issueDate", "2022-01-16"); + MetadatumDTO titleInline2 = createMetadatumDTO("crispatent", "document", "title", + "Automated test equipment for testing one or more devices under test," + + " method for automated testing of one or more devices under test," + + " and computer program using a buffer memory"); + metadatums2.add(identifierOther2); metadatums2.add(patentno2); + metadatums2.add(kind2); metadatums2.add(identifier2); metadatums2.add(date2); metadatums2.add(dateSubmitted2); - metadatums2.add(applicant3); - metadatums2.add(applicant4); + metadatums2.add(applicant2); metadatums2.add(author5); metadatums2.add(author6); metadatums2.add(author7); metadatums2.add(title2); metadatums2.add(subject2); metadatums2.add(subject3); + metadatums2.add(kindCodeInline2); + metadatums2.add(issueDateInline2); + metadatums2.add(titleInline2); ImportRecord secondRecord = new ImportRecord(metadatums2); records.add(firstrRecord); From 85b0dc3c1abaa4901ef5c715ef367dc5f5429ed6 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 24 May 2023 13:45:07 +0200 Subject: [PATCH 348/394] [DSC-1099] Improved null checks --- .../PersonStrictCustomSolrFilterImpl.java | 7 ++++-- ...rServiceStrictBestMatchIndexingPlugin.java | 22 +++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/authority/PersonStrictCustomSolrFilterImpl.java b/dspace-api/src/main/java/org/dspace/content/authority/PersonStrictCustomSolrFilterImpl.java index f8332311fe60..9a52db998af9 100644 --- a/dspace-api/src/main/java/org/dspace/content/authority/PersonStrictCustomSolrFilterImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/authority/PersonStrictCustomSolrFilterImpl.java @@ -10,6 +10,8 @@ import static org.apache.solr.client.solrj.util.ClientUtils.escapeQueryChars; import static org.dspace.discovery.SolrServiceStrictBestMatchIndexingPlugin.cleanNameWithStrictPolicies; +import java.util.Optional; + import org.dspace.discovery.SolrServiceStrictBestMatchIndexingPlugin; /** @@ -29,8 +31,9 @@ public String getSolrQuery(String searchTerm) { * @return solr query */ public String generateSearchQueryStrictBestMatch(String searchTerm) { - return SolrServiceStrictBestMatchIndexingPlugin.BEST_MATCH_INDEX + ":" - + escapeQueryChars(cleanNameWithStrictPolicies(searchTerm)); + return Optional.ofNullable(cleanNameWithStrictPolicies(searchTerm)) + .map(query -> SolrServiceStrictBestMatchIndexingPlugin.BEST_MATCH_INDEX + ":" + escapeQueryChars(query)) + .orElse(null); } @Override diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceStrictBestMatchIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceStrictBestMatchIndexingPlugin.java index 3ee84ce63642..d3154470a6af 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceStrictBestMatchIndexingPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceStrictBestMatchIndexingPlugin.java @@ -7,11 +7,11 @@ */ package org.dspace.discovery; -import static java.util.stream.Collectors.toSet; - import java.util.Collection; import java.util.HashSet; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.solr.common.SolrInputDocument; @@ -67,16 +67,19 @@ public Set getPossibleBestMatchValues(String firstName, String lastName, } public Set getPossibleBestMatchValues(Collection fullnames) { - - Set nameSet = new HashSet(); - // add all possible matches to the solr index - nameSet.addAll(fullnames.stream().map(SolrServiceStrictBestMatchIndexingPlugin::cleanNameWithStrictPolicies) - .collect(toSet())); - - return nameSet; + return fullnames + .stream() + .filter(Objects::nonNull) + // add all possible matches to the solr index + .map(SolrServiceStrictBestMatchIndexingPlugin::cleanNameWithStrictPolicies) + .collect(Collectors.toSet()); } public static String cleanNameWithStrictPolicies(String name) { + if (name == null) { + return null; + } + if (configurationService.getBooleanProperty(EXCLUDE_LETTER_CASE_CONFIG, true)) { name = name.toLowerCase(); } @@ -110,6 +113,7 @@ private static Set generateBaseNameSet(String firstName, String lastName return baseNameSet; } + @Override protected void addIndexValue(SolrInputDocument document, String value) { document.addField(BEST_MATCH_INDEX, value); } From 6218ea559cbabe8b2281c08d78ae4d6b61e162ea Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Thu, 25 May 2023 13:48:31 +0200 Subject: [PATCH 349/394] [DSC-1100] fix integration test --- .../app/rest/ScopusImportMetadataSourceServiceIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java index 84bf46c92920..879aac2c906a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScopusImportMetadataSourceServiceIT.java @@ -184,7 +184,7 @@ private ArrayList getRecords() { MetadatumDTO doi = createMetadatumDTO("dc", "identifier", null, "10.3934/mine.2023004"); MetadatumDTO title = createMetadatumDTO("dc","title", null, "Hardy potential versus lower order terms in Dirichlet problems: regularizing effects†"); - MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Journal"); + MetadatumDTO type = createMetadatumDTO("dc", "type", null, "Resource Types::text::journal::journal article"); MetadatumDTO date = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); MetadatumDTO scopusId = createMetadatumDTO("dc", "identifier", "scopus", "2-s2.0-85124241875"); MetadatumDTO citationVolume = createMetadatumDTO("oaire", "citation", "volume", "5"); @@ -206,7 +206,6 @@ private ArrayList getRecords() { metadatums.add(doi); metadatums.add(title); - metadatums.add(type); metadatums.add(date); metadatums.add(scopusId); metadatums.add(citationVolume); @@ -224,6 +223,7 @@ private ArrayList getRecords() { metadatums.add(author3); metadatums.add(scopusAuthorId3); metadatums.add(orgunit3); + metadatums.add(type); ImportRecord firstrRecord = new ImportRecord(metadatums); //define second record @@ -233,7 +233,7 @@ private ArrayList getRecords() { "Large deviations for a binary collision model: energy evaporation†"); MetadatumDTO date2 = createMetadatumDTO("dc", "date", "issued", "2023-01-01"); MetadatumDTO scopusId2 = createMetadatumDTO("dc", "identifier", "scopus", "2-s2.0-85124226483"); - MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "Journal"); + MetadatumDTO type2 = createMetadatumDTO("dc", "type", null, "Resource Types::text::journal::journal article"); MetadatumDTO citationVolume2 = createMetadatumDTO("oaire", "citation", "volume", "5"); MetadatumDTO citationIssue2 = createMetadatumDTO("oaire", "citation", "issue", "1"); @@ -258,7 +258,6 @@ private ArrayList getRecords() { "Mathematics In Engineering"); metadatums2.add(doi2); metadatums2.add(title2); - metadatums2.add(type2); metadatums2.add(date2); metadatums2.add(scopusId2); metadatums2.add(citationVolume2); @@ -279,6 +278,7 @@ private ArrayList getRecords() { metadatums2.add(author7); metadatums2.add(scopusAuthorId7); metadatums2.add(orgunit7); + metadatums2.add(type2); ImportRecord secondRecord = new ImportRecord(metadatums2); records.add(firstrRecord); records.add(secondRecord); From 9cb4c590303d3fd7e7a7184f15201c7eecb536ff Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 29 May 2023 16:00:10 +0200 Subject: [PATCH 350/394] [DSC-1092] fixed extraction of title with multi language --- .../EpoImportMetadataSourceServiceImpl.java | 2 +- dspace/config/spring/api/epo-integration.xml | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java index f5d5717b6eec..2ee114a3801a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java @@ -483,7 +483,7 @@ private List splitToRecords(String recordsSrc) { Document document = saxBuilder.build(new StringReader(recordsSrc)); Element root = document.getRootElement(); List namespaces = Arrays.asList(Namespace.getNamespace("ns", "http://www.epo.org/exchange")); - XPathExpression xpath = XPathFactory.instance().compile("//ns:exchange-document", + XPathExpression xpath = XPathFactory.instance().compile("//ns:exchange-documents", Filters.element(), null, namespaces); List recordsList = xpath.evaluate(root); diff --git a/dspace/config/spring/api/epo-integration.xml b/dspace/config/spring/api/epo-integration.xml index 4836fba7d4de..ef4ac5fc867e 100644 --- a/dspace/config/spring/api/epo-integration.xml +++ b/dspace/config/spring/api/epo-integration.xml @@ -57,18 +57,20 @@ - - - + + + + - + - + + @@ -95,9 +97,10 @@ - + - + + @@ -114,9 +117,10 @@ - + - + + From 885ee562aa1819f625e86d4bceac09e0d862829d Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 29 May 2023 16:02:57 +0200 Subject: [PATCH 351/394] [CST-10072] Fixed update procedure for item correction --- .../versioning/AbstractVersionProvider.java | 2 +- .../versioning/ItemCorrectionProvider.java | 76 ++++++++++++------- .../spring/api/item-correction-service.xml | 8 ++ 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java index 8d1b27e83546..318d59fd8732 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/AbstractVersionProvider.java @@ -125,7 +125,7 @@ public void setIgnoredMetadataFields(Set ignoredMetadataFields) { this.ignoredMetadataFields = ignoredMetadataFields; } - public Set getIgnoredMetadataFields() { + public Set getIgnoredMetadataFields() { if (ignoredMetadataFields == null) { return new HashSet<>(); } diff --git a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java index dad879e0a5d5..66c4cca3ea7c 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; @@ -24,7 +25,6 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.MetadataField; -import org.dspace.content.MetadataSchema; import org.dspace.content.MetadataValue; import org.dspace.content.RelationshipMetadataValue; import org.dspace.content.WorkspaceItem; @@ -84,15 +84,10 @@ public XmlWorkflowItem updateNativeItemWithCorrection(Context context, XmlWorkfl Set ignoredMetadataFieldsOfMerging = Sets.union(getIgnoredMetadataFields(), getIgnoredMetadataFieldsOfMerging()); - // save entity type - MetadataValue entityType = itemService.getMetadata(nativeItem, "dspace", "entity", "type", Item.ANY).get(0); - // clear all metadata entries from native item - itemService.clearMetadata(context, nativeItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + // clear all metadata that are not ignored inside the nativeItem + clearMetadataNotInSet(context, nativeItem, ignoredMetadataFieldsOfMerging); // copy metadata from corrected item to native item copyMetadata(context, nativeItem, correctionItem, ignoredMetadataFieldsOfMerging); - // restore entity type - itemService.addMetadata(context, nativeItem, entityType.getMetadataField(), entityType.getLanguage(), - entityType.getValue(), entityType.getAuthority(), entityType.getConfidence()); context.turnOffAuthorisationSystem(); // copy bundles and bitstreams of native item @@ -113,42 +108,71 @@ public XmlWorkflowItem updateNativeItemWithCorrection(Context context, XmlWorkfl context.restoreAuthSystemState(); - - - return workflowItem; } + private void clearMetadataNotInSet(Context context, Item nativeItem, Set ignoredMetadata) { + Iterator metadata = nativeItem.getMetadata().iterator(); + MetadataValue metadataValue = null; + while (!ignoredMetadata.isEmpty() && metadata.hasNext() && (metadataValue = metadata.next()) != null) { + if (!isIgnored(ignoredMetadata, metadataValue)) { + try { + this.itemService.clearMetadata( + context, nativeItem, + metadataValue.getSchema(), + metadataValue.getElement(), + metadataValue.getQualifier(), + Item.ANY + ); + } catch (SQLException e) { + throw new RuntimeException("Cannot clear not ignored Metadata!", e); + } + } + } + } + private void copyMetadata(Context context, Item itemNew, Item nativeItem, Set ignoredMetadataFields) throws SQLException { - - List md = itemService.getMetadata(nativeItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); - for (MetadataValue aMd : md) { - MetadataField metadataField = aMd.getMetadataField(); - MetadataSchema metadataSchema = metadataField.getMetadataSchema(); - String unqualifiedMetadataField = metadataSchema.getName() + "." + metadataField.getElement(); - if (ignoredMetadataFields.contains(metadataField.toString('.')) || - ignoredMetadataFields.contains(unqualifiedMetadataField + "." + Item.ANY) || - aMd instanceof RelationshipMetadataValue) { + List metadataList = itemService.getMetadata(nativeItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + for (MetadataValue metadataValue : metadataList) { + if (isIgnored(ignoredMetadataFields, metadataValue)) { //Skip this metadata field (ignored and/or virtual) continue; } - + MetadataField metadataField = metadataValue.getMetadataField(); itemService.addMetadata( context, itemNew, metadataField.getMetadataSchema().getName(), metadataField.getElement(), metadataField.getQualifier(), - aMd.getLanguage(), - aMd.getValue(), - aMd.getAuthority(), - aMd.getConfidence(), - aMd.getPlace() + metadataValue.getLanguage(), + metadataValue.getValue(), + metadataValue.getAuthority(), + metadataValue.getConfidence(), + metadataValue.getPlace() ); } } + protected boolean isIgnored(Set ignoredMetadataFields, MetadataValue metadataValue) { + return metadataValue instanceof RelationshipMetadataValue || + isIgnoredWithQualifier(ignoredMetadataFields, metadataValue.getMetadataField()) || + isIgnoredAnyQualifier(ignoredMetadataFields, metadataValue.getMetadataField()); + } + + private boolean isIgnoredAnyQualifier(Set ignoredMetadataFields, MetadataField metadataField) { + return ignoredMetadataFields.contains(getUnqualifiedMetadata(metadataField)); + } + + private String getUnqualifiedMetadata(MetadataField metadataField) { + return metadataField.getMetadataSchema().getName() + "." + metadataField.getElement() + "." + Item.ANY; + } + + private boolean isIgnoredWithQualifier(Set ignoredMetadataFields, MetadataField metadataField) { + return ignoredMetadataFields.contains(metadataField.toString('.')); + } + protected void updateBundlesAndBitstreams(Context c, Item itemNew, Item nativeItem) throws SQLException, AuthorizeException, IOException { diff --git a/dspace/config/spring/api/item-correction-service.xml b/dspace/config/spring/api/item-correction-service.xml index 8958b33b916a..f1478f81f380 100644 --- a/dspace/config/spring/api/item-correction-service.xml +++ b/dspace/config/spring/api/item-correction-service.xml @@ -22,6 +22,14 @@ dc.date.available dspace.entity.type + + + + + + + + From a50bdf37a97357d4af34a1ab091da8933ecd9731 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 29 May 2023 17:23:26 +0200 Subject: [PATCH 352/394] [DSC-670] Fixed test configuration --- .../src/test/data/dspaceFolder/config/spring/api/scripts.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index 6a3830f616d6..0697423578bc 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -151,4 +151,9 @@ + + + + + From a01cf4de4c3623b6dfb35ded79fdef729c05c255 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 29 May 2023 17:55:01 +0200 Subject: [PATCH 353/394] [DSC-1030] Fix exception message --- .../patch/operation/DSpaceObjectMetadataAddOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataAddOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataAddOperation.java index a85da31fd999..fe065941bd80 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataAddOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/DSpaceObjectMetadataAddOperation.java @@ -74,7 +74,7 @@ private void add(Context context, DSpaceObject dso, DSpaceObjectService dsoServi if (dso instanceof Item) { if (!itemConverter.checkMetadataFieldVisibility(context, (Item) dso, metadataField)) { throw new UnprocessableEntityException( - "Current user has not permession to esecute patch peration on " + metadataField); + "Current user has not permission to execute patch operation on " + metadataField); } } int indexInt = 0; From 51e489fe5a3f18d5fe33548b042dd42e5236d32c Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 29 May 2023 18:26:12 +0200 Subject: [PATCH 354/394] [CST-10072] Fix metadata delete ignored list --- .../java/org/dspace/versioning/ItemCorrectionProvider.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java index 66c4cca3ea7c..ee56ee3e9579 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java @@ -81,11 +81,13 @@ public WorkspaceItem createNewItemAndAddItInWorkspace(Context context, Collectio public XmlWorkflowItem updateNativeItemWithCorrection(Context context, XmlWorkflowItem workflowItem, Item correctionItem, Item nativeItem) throws AuthorizeException, IOException, SQLException { + Set ignoredMetadataFieldsOfCreation = + Sets.union(getIgnoredMetadataFields(), getIgnoredMetadataFieldsOfCreation()); Set ignoredMetadataFieldsOfMerging = Sets.union(getIgnoredMetadataFields(), getIgnoredMetadataFieldsOfMerging()); // clear all metadata that are not ignored inside the nativeItem - clearMetadataNotInSet(context, nativeItem, ignoredMetadataFieldsOfMerging); + clearMetadataNotInSet(context, nativeItem, ignoredMetadataFieldsOfCreation); // copy metadata from corrected item to native item copyMetadata(context, nativeItem, correctionItem, ignoredMetadataFieldsOfMerging); From 65dbec0a9e1e93c65ea90d36349f8302ded31685 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 29 May 2023 18:40:14 +0200 Subject: [PATCH 355/394] [CST-10072] Fixes ConcurrentModificationException --- .../versioning/ItemCorrectionProvider.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java index ee56ee3e9579..0a031ede318f 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java @@ -114,20 +114,28 @@ public XmlWorkflowItem updateNativeItemWithCorrection(Context context, XmlWorkfl } private void clearMetadataNotInSet(Context context, Item nativeItem, Set ignoredMetadata) { + Set metadatasToClear = new HashSet<>(); Iterator metadata = nativeItem.getMetadata().iterator(); MetadataValue metadataValue = null; while (!ignoredMetadata.isEmpty() && metadata.hasNext() && (metadataValue = metadata.next()) != null) { if (!isIgnored(ignoredMetadata, metadataValue)) { + metadatasToClear.add(metadataValue.getMetadataField()); + } + } + if (!metadatasToClear.isEmpty()) { + for (MetadataField metadataField : metadatasToClear) { try { this.itemService.clearMetadata( context, nativeItem, - metadataValue.getSchema(), - metadataValue.getElement(), - metadataValue.getQualifier(), + metadataField.getMetadataSchema().getName(), + metadataField.getElement(), + metadataField.getQualifier(), Item.ANY ); } catch (SQLException e) { - throw new RuntimeException("Cannot clear not ignored Metadata!", e); + throw new RuntimeException( + "Cannot clear not ignored Metadata: " + metadataField.toString(), e + ); } } } @@ -137,7 +145,7 @@ private void copyMetadata(Context context, Item itemNew, Item nativeItem, Set metadataList = itemService.getMetadata(nativeItem, Item.ANY, Item.ANY, Item.ANY, Item.ANY); for (MetadataValue metadataValue : metadataList) { - if (isIgnored(ignoredMetadataFields, metadataValue)) { + if (isRelationshipOrIgnored(ignoredMetadataFields, metadataValue)) { //Skip this metadata field (ignored and/or virtual) continue; } @@ -157,10 +165,13 @@ private void copyMetadata(Context context, Item itemNew, Item nativeItem, Set ignoredMetadataFields, MetadataValue metadataValue) { + return metadataValue instanceof RelationshipMetadataValue || isIgnored(ignoredMetadataFields, metadataValue); + } + protected boolean isIgnored(Set ignoredMetadataFields, MetadataValue metadataValue) { - return metadataValue instanceof RelationshipMetadataValue || - isIgnoredWithQualifier(ignoredMetadataFields, metadataValue.getMetadataField()) || - isIgnoredAnyQualifier(ignoredMetadataFields, metadataValue.getMetadataField()); + return isIgnoredWithQualifier(ignoredMetadataFields, metadataValue.getMetadataField()) || + isIgnoredAnyQualifier(ignoredMetadataFields, metadataValue.getMetadataField()); } private boolean isIgnoredAnyQualifier(Set ignoredMetadataFields, MetadataField metadataField) { From 832639a24c6def9e1bf824a0295d01060bcc1cc5 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 31 May 2023 17:22:40 +0200 Subject: [PATCH 356/394] [DSC-1112] Force item update during bulk import even if no action is specified --- .../org/dspace/app/bulkedit/BulkImport.java | 4 +--- .../assetstore/bulk-import/update-items.xls | Bin 6144 -> 6144 bytes 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkImport.java index fbb904f8084e..491039cff835 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkImport.java @@ -1158,9 +1158,6 @@ private Item updateItem(EntityRow entityRow, Item item) handler.logInfo("Row " + entityRow.getRow() + " - Item updated successfully - ID: " + item.getID()); switch (entityRow.getAction()) { - case UPDATE: - itemService.update(context, item); - break; case UPDATE_WORKFLOW: startWorkflow(entityRow, item); break; @@ -1168,6 +1165,7 @@ private Item updateItem(EntityRow entityRow, Item item) installItem(entityRow, item); break; default: + itemService.update(context, item); break; } diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/bulk-import/update-items.xls b/dspace-api/src/test/data/dspaceFolder/assetstore/bulk-import/update-items.xls index 01d923643cf003a01ac1b97d9dba3b8e23940d1a..2d30ccfd275f489d5c688753db6ba809f06321de 100644 GIT binary patch delta 135 zcmZoLXfW6y%)_kCpgviQC!djfa~qE!BfDRIQEp;R>g0L6ig5NZ-d@HDlSBA!b22iq zF)%SOGKfyL;a|bW1!OS*F&7XrGEV-)?>E_ig=MpYfF&ch5d(t*P_p56w)f-~K?RNn c6~9X_O}@!ES&>C#^8rB%W=7u4KSkJ?0U`?{cK`qY delta 135 zcmZoLXfW6y%)`vcz&KfqC!djLa~qE!BYRM4dTCB#(d2o&ig5NZ-d@H@lSBA!^ZsSv zVqjumWDsRwVPKeS!M}o!1;}Rrim(776Vv2({C=Aq1S}c34Hy_4fD#NkjvA9&1Qj?q cla)nwCf{V7tjHp=`GBAWGo#? Date: Fri, 9 Jun 2023 12:11:32 +0200 Subject: [PATCH 357/394] [DSC-1118][CST-10501] Fixes wrong check for user in special group --- .../security/CrisSecurityServiceImpl.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java index 572cf1eec26c..1d670544e299 100644 --- a/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import org.apache.commons.collections.CollectionUtils; @@ -176,27 +177,38 @@ private boolean hasAccessByGroup(Context context, EPerson user, List gro return false; } - try { - for (Group group : context.getSpecialGroups()) { - if (groupService.isMember(context, user, group)) { - return true; - } - } - } catch (SQLException e) { - throw new SQLRuntimeException(e.getMessage(), e); - } - List userGroups = user.getGroups(); if (CollectionUtils.isEmpty(userGroups)) { return false; } return groups.stream() - .map(group -> findGroupByNameOrUUID(context, group)) + .map(group -> findGroupOrSpecialGroups(context, group)) .filter(group -> Objects.nonNull(group)) .anyMatch(group -> userGroups.contains(group)); } + private Group findGroupOrSpecialGroups(Context context, String group) { + return Optional.ofNullable(findGroupByNameOrUUID(context, group)) + .or(() -> Optional.ofNullable(findInSpecialGroups(context, group))) + .orElse(null); + } + + private Group findInSpecialGroups(Context context, String group) { + try { + return context.getSpecialGroups() + .stream() + .filter(specialGroup -> + specialGroup.getName().equals(group) || + specialGroup.getID().toString().equals(group) + ) + .findFirst() + .orElse(null); + } catch (SQLException e) { + throw new SQLRuntimeException(e.getMessage(), e); + } + } + private Group findGroupByNameOrUUID(Context context, String group) { try { UUID groupUUID = UUIDUtils.fromString(group); From d41e1689aff9d138d753b0de6261a94d598deb75 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 9 Jun 2023 15:30:06 +0200 Subject: [PATCH 358/394] [DSC-1118][CST-10501] Fixes AuthorizationRestRepositoryIT --- .../security/CrisSecurityServiceImpl.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java index 1d670544e299..4a8b2c313846 100644 --- a/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/security/CrisSecurityServiceImpl.java @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.UUID; import org.apache.commons.collections.CollectionUtils; @@ -183,25 +182,20 @@ private boolean hasAccessByGroup(Context context, EPerson user, List gro } return groups.stream() - .map(group -> findGroupOrSpecialGroups(context, group)) + .map(group -> findGroupByNameOrUUID(context, group)) .filter(group -> Objects.nonNull(group)) - .anyMatch(group -> userGroups.contains(group)); + .anyMatch(group -> userGroups.contains(group) || isSpecialGroup(context, group)); } - private Group findGroupOrSpecialGroups(Context context, String group) { - return Optional.ofNullable(findGroupByNameOrUUID(context, group)) - .or(() -> Optional.ofNullable(findInSpecialGroups(context, group))) - .orElse(null); + private boolean isSpecialGroup(Context context, Group group) { + return findInSpecialGroups(context, group) != null; } - private Group findInSpecialGroups(Context context, String group) { + private Group findInSpecialGroups(Context context, Group group) { try { return context.getSpecialGroups() .stream() - .filter(specialGroup -> - specialGroup.getName().equals(group) || - specialGroup.getID().toString().equals(group) - ) + .filter(specialGroup -> specialGroup != null && specialGroup.equals(group)) .findFirst() .orElse(null); } catch (SQLException e) { From 9ec1433b8b09170efb9f96b9921a89833c1c4427 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 9 Jun 2023 18:28:26 +0200 Subject: [PATCH 359/394] [maven-release-plugin] prepare release dspace-cris-2023.01.00 --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 27c845a239fe..1c0d7d63c1e7 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index 359e6902e52f..b04e605cc6b2 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 236c7c88eb67..2502e9e8ab84 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 8e6e09f94f69..3ffcc9c14289 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index ce6a94b64cb9..4fbd66da25a2 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 2fd0a16eaf28..468adbed4c57 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index 01cfe032386a..e8ea22fa4910 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index c5f47cbc9479..e541f717210f 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 8a8b811a161d..add7a2718ab1 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 1397cbfd306f..96d46b9a85e5 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index c0d4078e5a75..3d506fbf5d18 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 73ead2c3911b..5f7c053d525b 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 5d6ce7978cb4..2b21dca54f75 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 .. diff --git a/dspace/pom.xml b/dspace/pom.xml index 8bc4a76e29e1..a311ee50f672 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 ../pom.xml diff --git a/pom.xml b/pom.xml index a4b2eca9513b..b61659c04bd1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 DSpace Parent Project DSpace-CRIS is an open source extension of DSpace (http://www.dspace.org) providing out of box support for the CRIS / RIMS and moder Institution Repository use cases with advanced features and optimized configurations @@ -958,14 +958,14 @@ org.dspace dspace-rest - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 jar classes org.dspace dspace-rest - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 war @@ -1116,69 +1116,69 @@ org.dspace dspace-api - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 org.dspace dspace-api test-jar - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 test org.dspace.modules additions - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 org.dspace dspace-sword - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 org.dspace dspace-swordv2 - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 org.dspace dspace-oai - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 org.dspace dspace-services - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 org.dspace dspace-server-webapp test-jar - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 test org.dspace dspace-rdf - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 org.dspace dspace-iiif - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 org.dspace dspace-server-webapp - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 jar classes org.dspace dspace-server-webapp - cris-2023.01.00-SNAPSHOT + cris-2023.01.00 war @@ -2029,7 +2029,7 @@ scm:git:git@github.com:4Science/DSpace.git scm:git:git@github.com:4Science/DSpace.git git@github.com:4Science/DSpace.git - dspace-cris-2022.02.00 + dspace-cris-2023.01.00 From e5bd22b88ebf564f410e8d0459c6f58f91077813 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 9 Jun 2023 18:28:31 +0200 Subject: [PATCH 360/394] [maven-release-plugin] prepare for next development iteration --- dspace-api/pom.xml | 2 +- dspace-iiif/pom.xml | 2 +- dspace-oai/pom.xml | 2 +- dspace-rdf/pom.xml | 2 +- dspace-rest/pom.xml | 4 ++-- dspace-server-webapp/pom.xml | 2 +- dspace-services/pom.xml | 2 +- dspace-sword/pom.xml | 2 +- dspace-swordv2/pom.xml | 2 +- dspace/modules/additions/pom.xml | 2 +- dspace/modules/pom.xml | 2 +- dspace/modules/rest/pom.xml | 2 +- dspace/modules/server/pom.xml | 2 +- dspace/pom.xml | 2 +- pom.xml | 32 ++++++++++++++++---------------- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 1c0d7d63c1e7..c70f5a2a6370 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -12,7 +12,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml index b04e605cc6b2..97704a14dfef 100644 --- a/dspace-iiif/pom.xml +++ b/dspace-iiif/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 2502e9e8ab84..5c6050d48a4a 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -8,7 +8,7 @@ dspace-parent org.dspace - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 3ffcc9c14289..c6d887b773c0 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 4fbd66da25a2..6a5945560682 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -3,7 +3,7 @@ org.dspace dspace-rest war - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT DSpace (Deprecated) REST Webapp DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED. Please consider using the REST API in the dspace-server-webapp instead! @@ -12,7 +12,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 468adbed4c57..5ad15c1097b5 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index e8ea22fa4910..8cb88f45781d 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -9,7 +9,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index e541f717210f..16f8a396fbee 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -15,7 +15,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index add7a2718ab1..b35fb7388a15 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -13,7 +13,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace/modules/additions/pom.xml b/dspace/modules/additions/pom.xml index 96d46b9a85e5..63d68c380bf0 100644 --- a/dspace/modules/additions/pom.xml +++ b/dspace/modules/additions/pom.xml @@ -17,7 +17,7 @@ org.dspace modules - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace/modules/pom.xml b/dspace/modules/pom.xml index 3d506fbf5d18..6c2fd62d85e6 100644 --- a/dspace/modules/pom.xml +++ b/dspace/modules/pom.xml @@ -11,7 +11,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT ../../pom.xml diff --git a/dspace/modules/rest/pom.xml b/dspace/modules/rest/pom.xml index 5f7c053d525b..eb5975f6323f 100644 --- a/dspace/modules/rest/pom.xml +++ b/dspace/modules/rest/pom.xml @@ -13,7 +13,7 @@ org.dspace modules - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace/modules/server/pom.xml b/dspace/modules/server/pom.xml index 2b21dca54f75..08ad7961e48c 100644 --- a/dspace/modules/server/pom.xml +++ b/dspace/modules/server/pom.xml @@ -13,7 +13,7 @@ just adding new jar in the classloader modules org.dspace - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT .. diff --git a/dspace/pom.xml b/dspace/pom.xml index a311ee50f672..e943a66aee09 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -16,7 +16,7 @@ org.dspace dspace-parent - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index b61659c04bd1..b25632a959ff 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.dspace dspace-parent pom - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT DSpace Parent Project DSpace-CRIS is an open source extension of DSpace (http://www.dspace.org) providing out of box support for the CRIS / RIMS and moder Institution Repository use cases with advanced features and optimized configurations @@ -958,14 +958,14 @@ org.dspace dspace-rest - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT jar classes org.dspace dspace-rest - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT war @@ -1116,69 +1116,69 @@ org.dspace dspace-api - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT org.dspace dspace-api test-jar - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT test org.dspace.modules additions - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT org.dspace dspace-sword - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT org.dspace dspace-swordv2 - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT org.dspace dspace-oai - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT org.dspace dspace-services - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT org.dspace dspace-server-webapp test-jar - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT test org.dspace dspace-rdf - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT org.dspace dspace-iiif - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT org.dspace dspace-server-webapp - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT jar classes org.dspace dspace-server-webapp - cris-2023.01.00 + cris-2023.01.01-SNAPSHOT war @@ -2029,7 +2029,7 @@ scm:git:git@github.com:4Science/DSpace.git scm:git:git@github.com:4Science/DSpace.git git@github.com:4Science/DSpace.git - dspace-cris-2023.01.00 + dspace-cris-2022.02.00 From 69f8b91abc8e42c2371763a0e169b1d837189b21 Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Mon, 12 Jun 2023 09:46:22 +0200 Subject: [PATCH 361/394] [DSC-1089] Sherpa Romeo policies do not update (bug) --- dspace/config/spring/api/sherpa.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/spring/api/sherpa.xml b/dspace/config/spring/api/sherpa.xml index 0414f3f8e4b4..eb299e4e2644 100644 --- a/dspace/config/spring/api/sherpa.xml +++ b/dspace/config/spring/api/sherpa.xml @@ -17,7 +17,7 @@ - dc.identifier.issn + dc.relation.issn From c22390bee0b77f816ff7699857f7a696c5e7c1a5 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 21 Jun 2023 17:45:41 +0200 Subject: [PATCH 362/394] [DSC-1130] fix subscriptions feature --- .../impl/CanSubscribeFeature.java | 4 ++- .../SubscriptionRestRepository.java | 30 +++++++------------ .../api/subscriptions_email_configuration.xml | 10 +++---- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java index 2e0e27b05751..fb1398448691 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/CanSubscribeFeature.java @@ -17,6 +17,7 @@ import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; @@ -55,7 +56,8 @@ public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLEx public String[] getSupportedTypes() { return new String[]{ CommunityRest.CATEGORY + "." + CommunityRest.NAME, - CollectionRest.CATEGORY + "." + CollectionRest.NAME + CollectionRest.CATEGORY + "." + CollectionRest.NAME, + ItemRest.CATEGORY + "." + ItemRest.NAME }; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java index ce1bcff11fbc..19970e828409 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubscriptionRestRepository.java @@ -9,8 +9,6 @@ import static org.dspace.app.rest.model.SubscriptionRest.CATEGORY; import static org.dspace.app.rest.model.SubscriptionRest.NAME; -import static org.dspace.core.Constants.COLLECTION; -import static org.dspace.core.Constants.COMMUNITY; import static org.dspace.core.Constants.READ; import java.io.IOException; @@ -177,24 +175,18 @@ protected SubscriptionRest createAndReturn(Context context) throws SQLException, } } - if (dSpaceObject.getType() == COMMUNITY || dSpaceObject.getType() == COLLECTION) { - Subscription subscription = null; - ServletInputStream input = req.getInputStream(); - SubscriptionRest subscriptionRest = new ObjectMapper().readValue(input, SubscriptionRest.class); - List subscriptionParameterList = subscriptionRest - .getSubscriptionParameterList(); - if (CollectionUtils.isNotEmpty(subscriptionParameterList)) { - List subscriptionParameters = new ArrayList<>(); - validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); - subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, - subscriptionRest.getSubscriptionType()); - } - context.commit(); - return converter.toRest(subscription, utils.obtainProjection()); - } else { - throw new DSpaceBadRequestException( - "Currently subscription is supported only for Community and Collection"); + Subscription subscription = null; + ServletInputStream input = req.getInputStream(); + SubscriptionRest subscriptionRest = new ObjectMapper().readValue(input, SubscriptionRest.class); + List subscriptionParameterList = subscriptionRest.getSubscriptionParameterList(); + if (CollectionUtils.isNotEmpty(subscriptionParameterList)) { + List subscriptionParameters = new ArrayList<>(); + validateParameters(subscriptionRest, subscriptionParameterList, subscriptionParameters); + subscription = subscribeService.subscribe(context, ePerson, dSpaceObject, subscriptionParameters, + subscriptionRest.getSubscriptionType()); } + context.commit(); + return converter.toRest(subscription, utils.obtainProjection()); } catch (SQLException sqlException) { throw new SQLException(sqlException.getMessage(), sqlException); } catch (IOException ioException) { diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index 2d52dfc27089..488bede0f385 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -1,12 +1,9 @@ + @@ -35,9 +33,9 @@ - - - + + + From 105c1268cfef82c45bb119d554490e70c21f6eb3 Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Thu, 22 Jun 2023 09:48:20 +0200 Subject: [PATCH 363/394] [IIIF-127] enable preview plugin for filter-media process to generate preview images with a maximux resolution of 1000x1000 --- dspace/config/dspace.cfg | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 65405e795af8..2fa3578cd90e 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -466,6 +466,7 @@ useProxies = true filter.plugins = Text Extractor filter.plugins = JPEG Thumbnail filter.plugins = PDFBox JPEG Thumbnail +filter.plugins = Branded Preview JPEG # [To enable Branded Preview]: uncomment and insert the following into the plugin list @@ -1110,8 +1111,8 @@ thumbnail.hqscaling = true #### Settings for BrandedPreviewJPEGFilter #### # max dimensions of the preview image -webui.preview.maxwidth = 600 -webui.preview.maxheight = 600 +webui.preview.maxwidth = 1000 +webui.preview.maxheight = 1000 # Blur before scaling. A little blur before scaling does wonders for keeping # moire in check. From be01030bea0cad6783d94f5713c05a6ab76e7c42 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 22 Jun 2023 10:28:27 +0200 Subject: [PATCH 364/394] [DSC-1130] fix failed tests --- .../rest/SubscriptionRestRepositoryIT.java | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java index 038acf7e73a6..6a086cc6b75d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubscriptionRestRepositoryIT.java @@ -572,13 +572,25 @@ public void createSubscriptionForItemByEPersonTest() throws Exception { context.restoreAuthSystemState(); + AtomicReference idRef = new AtomicReference(); String tokenEPerson = getAuthToken(eperson.getEmail(), password); - getClient(tokenEPerson).perform(post("/api/core/subscriptions") - .param("resource", item1.getID().toString()) - .param("eperson_id", eperson.getID().toString()) - .content(new ObjectMapper().writeValueAsString(map)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isBadRequest()); + + try { + getClient(tokenEPerson).perform(post("/api/core/subscriptions") + .param("resource", item1.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("resource"))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + SubscribeBuilder.deleteSubscription(idRef.get()); + } } @Test @@ -600,13 +612,25 @@ public void createSubscriptionForItemByAdminTest() throws Exception { context.restoreAuthSystemState(); + AtomicReference idRef = new AtomicReference(); String tokenAdmin = getAuthToken(admin.getEmail(), password); - getClient(tokenAdmin).perform(post("/api/core/subscriptions") - .param("resource", item1.getID().toString()) - .param("eperson_id", eperson.getID().toString()) - .content(new ObjectMapper().writeValueAsString(map)) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isBadRequest()); + + try { + getClient(tokenAdmin).perform(post("/api/core/subscriptions") + .param("resource", item1.getID().toString()) + .param("eperson_id", eperson.getID().toString()) + .content(new ObjectMapper().writeValueAsString(map)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.subscriptionType", is("content"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].name", is("frequency"))) + .andExpect(jsonPath("$.subscriptionParameterList[0].value", is("W"))) + .andExpect(jsonPath("$._links.eperson.href", Matchers.endsWith("eperson"))) + .andExpect(jsonPath("$._links.resource.href", Matchers.endsWith("resource"))) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + } finally { + SubscribeBuilder.deleteSubscription(idRef.get()); + } } @Test From 600942d2815fefeffc378172a2a17c8426e85fa5 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 22 Jun 2023 13:04:39 +0200 Subject: [PATCH 365/394] [DSC-1130] fix failed tests --- .../app/rest/authorization/CanSubscribeFeatureIT.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java index 7eb0960566fc..abb52f374ba1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CanSubscribeFeatureIT.java @@ -175,10 +175,12 @@ public void canNotSubscribeItemTest() throws Exception { String token3 = getAuthToken(ePersonNotSubscribePermission.getEmail(), password); getClient(token1).perform(get("/api/authz/authorizations/" + epersonToItem.getID())) - .andExpect(status().isNotFound()); + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(AuthorizationMatcher.matchAuthorization(epersonToItem)))); getClient(token2).perform(get("/api/authz/authorizations/" + adminToItem.getID())) - .andExpect(status().isNotFound()); + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is(AuthorizationMatcher.matchAuthorization(adminToItem)))); getClient(token3).perform(get("/api/authz/authorizations/" + ePersonNotSubscribePermissionToItem.getID())) .andExpect(status().isNotFound()); From 1a397a833ef749b0e69e04e76d830d26af7eb16c Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Thu, 22 Jun 2023 13:27:40 +0200 Subject: [PATCH 366/394] [DSCR-1130] fix failed test --- .../rest/authorization/GenericAuthorizationFeatureIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java index 1d3b5b051605..a93a964d36de 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/GenericAuthorizationFeatureIT.java @@ -16,7 +16,6 @@ import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.authorize.ResourcePolicy; import org.dspace.builder.BitstreamBuilder; import org.dspace.builder.BundleBuilder; import org.dspace.builder.CollectionBuilder; @@ -757,7 +756,8 @@ public void testCanMoveAdmin() throws Exception { // Verify the general admin has this feature on item 1 getClient(adminToken).perform( get("/api/authz/authorizations/search/object?embed=feature&uri=" - + "http://localhost/api/core/items/" + item1.getID())) + + "http://localhost/api/core/items/" + item1.getID()) + .param("size", "30")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.authorizations[?(@._embedded.feature.id=='" + feature + "')]").exists()); @@ -798,7 +798,7 @@ public void testCanMoveAdmin() throws Exception { // grant item 1 admin REMOVE permissions on the item’s owning collection // verify item 1 admin has this feature on item 1 context.turnOffAuthorisationSystem(); - ResourcePolicy removePermission = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context) .withDspaceObject(collectionX) .withAction(Constants.REMOVE) .withUser(item1Writer) @@ -820,7 +820,7 @@ public void testCanMoveWriter() throws Exception { // grant item 1 write REMOVE permissions on the item’s owning collection context.turnOffAuthorisationSystem(); - ResourcePolicy removePermission = ResourcePolicyBuilder.createResourcePolicy(context) + ResourcePolicyBuilder.createResourcePolicy(context) .withDspaceObject(collectionX) .withAction(Constants.REMOVE) .withUser(item1Writer) From 995d39617c92dd2e20cb89ecbf4a3f709c2bad82 Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Thu, 22 Jun 2023 13:52:34 +0200 Subject: [PATCH 367/394] [IIIF-127] fix branded preview plugin to extend jpeg plugin on filter media --- .../app/mediafilter/BrandedPreviewJPEGFilter.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java index 7b082c6c21a4..7073f4f09776 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/BrandedPreviewJPEGFilter.java @@ -22,7 +22,7 @@ * * @author Jason Sherman jsherman@usao.edu */ -public class BrandedPreviewJPEGFilter extends MediaFilter { +public class BrandedPreviewJPEGFilter extends JPEGFilter { @Override public String getFilteredName(String oldFilename) { return oldFilename + ".preview.jpg"; @@ -36,14 +36,6 @@ public String getBundleName() { return "BRANDED_PREVIEW"; } - /** - * @return String bitstreamformat - */ - @Override - public String getFormatString() { - return "JPEG"; - } - /** * @return String description */ @@ -81,9 +73,7 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo String brandFont = configurationService.getProperty("webui.preview.brand.font"); int brandFontPoint = configurationService.getIntProperty("webui.preview.brand.fontpoint"); - JPEGFilter jpegFilter = new JPEGFilter(); - return jpegFilter - .getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, + return getThumbDim(currentItem, buf, verbose, xmax, ymax, blurring, hqscaling, brandHeight, brandFontPoint, brandFont); } } From db54c8737e6639673169c97a97b23c40d6419961 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 29 Jun 2023 17:36:40 +0200 Subject: [PATCH 368/394] [DSC-1146] Fixed issue with item correction, for which if native bundle was empty, new bitstreams were not copied --- .../dspace/versioning/ItemCorrectionProvider.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java index 0a031ede318f..75efd7ae6965 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java @@ -193,10 +193,19 @@ protected void updateBundlesAndBitstreams(Context c, Item itemNew, Item nativeIt List nativeBundles = nativeItem.getBundles(bundleName); List correctedBundles = itemNew.getBundles(bundleName); - if (CollectionUtils.isEmpty(nativeBundles) || CollectionUtils.isEmpty(correctedBundles)) { + if (CollectionUtils.isEmpty(nativeBundles) && CollectionUtils.isEmpty(correctedBundles) || + CollectionUtils.isEmpty(correctedBundles)) { continue; } - updateBundleAndBitstreams(c, nativeBundles.get(0), correctedBundles.get(0)); + + Bundle nativeBundle; + if (CollectionUtils.isEmpty(nativeBundles)) { + nativeBundle = bundleService.create(c, nativeItem, bundleName); + } else { + nativeBundle = nativeBundles.get(0); + } + + updateBundleAndBitstreams(c, nativeBundle, correctedBundles.get(0)); } } From e94d877e6d9bfcfc801890dffbed8a4d9fe81478 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 30 Jun 2023 17:18:45 +0200 Subject: [PATCH 369/394] [DSC-573] Fix test --- .../org/dspace/app/rest/SubmissionDefinitionsControllerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index 56daeee2e31c..65d61ba0812b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -322,7 +322,7 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=15"), Matchers.containsString("size=1")))) + Matchers.containsString("page=16"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) .andExpect(jsonPath("$.page.totalElements", is(16))) .andExpect(jsonPath("$.page.totalPages", is(16))) From fb07f4e34caea00bff494ecbcc30c1682b60d79e Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 30 Jun 2023 19:04:02 +0200 Subject: [PATCH 370/394] [DSC-573] Fix test --- .../dspace/app/rest/SubmissionDefinitionsControllerIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index 65d61ba0812b..818b1c7d63c7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -322,10 +322,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=16"), Matchers.containsString("size=1")))) + Matchers.containsString("page=17"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(16))) - .andExpect(jsonPath("$.page.totalPages", is(16))) + .andExpect(jsonPath("$.page.totalElements", is(17))) + .andExpect(jsonPath("$.page.totalPages", is(17))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") From f7abfa50ee983b3b2b4c06e322ebceefdeafd6c8 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 30 Jun 2023 20:48:37 +0200 Subject: [PATCH 371/394] [DSC-573] Fix test --- .../app/rest/SubmissionDefinitionsControllerIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index 818b1c7d63c7..a0343d67e93d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -322,7 +322,7 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=17"), Matchers.containsString("size=1")))) + Matchers.containsString("page=16"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) .andExpect(jsonPath("$.page.totalElements", is(17))) .andExpect(jsonPath("$.page.totalPages", is(17))) @@ -348,10 +348,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=15"), Matchers.containsString("size=1")))) + Matchers.containsString("page=16"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(16))) - .andExpect(jsonPath("$.page.totalPages", is(16))) + .andExpect(jsonPath("$.page.totalElements", is(17))) + .andExpect(jsonPath("$.page.totalPages", is(17))) .andExpect(jsonPath("$.page.number", is(1))); } From afd7650c817a8346bc7742e66eb40c1916086994 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 3 Jul 2023 17:42:05 +0200 Subject: [PATCH 372/394] [DSC-1092] fix empty response from external services --- .../ADSImportMetadataSourceServiceImpl.java | 6 +++++ .../CiniiImportMetadataSourceServiceImpl.java | 6 +++++ ...ossRefImportMetadataSourceServiceImpl.java | 12 +++++++++ .../EpoImportMetadataSourceServiceImpl.java | 27 +++++++++++++------ ...PubmedEuropeMetadataSourceServiceImpl.java | 3 +++ ...ScopusImportMetadataSourceServiceImpl.java | 12 +++++++++ ...VuFindImportMetadataSourceServiceImpl.java | 9 +++++-- .../WOSImportMetadataSourceServiceImpl.java | 9 +++++++ 8 files changed, 74 insertions(+), 10 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java index 8fbe4ef2cf57..da59472c45a6 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/ads/ADSImportMetadataSourceServiceImpl.java @@ -273,6 +273,9 @@ public Integer count(String query, String token) { uriBuilder.addParameter("fl", this.resultFieldList); String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return 0; + } JsonNode jsonNode = convertStringJsonToJsonNode(resp); return jsonNode.at("/response/numFound").asInt(); } catch (URISyntaxException e) { @@ -296,6 +299,9 @@ public List search(String query, Integer start, Integer count, Str uriBuilder.addParameter("fl", this.resultFieldList); String resp = liveImportClient.executeHttpGetRequest(timeout, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return adsResults; + } JsonNode jsonNode = convertStringJsonToJsonNode(resp); JsonNode docs = jsonNode.at("/response/docs"); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java index 5eff46c790e4..53230d960831 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/cinii/CiniiImportMetadataSourceServiceImpl.java @@ -289,6 +289,9 @@ protected List search(String id, String appId) URIBuilder uriBuilder = new URIBuilder(this.url + id + ".rdf?appid=" + appId); Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isBlank(response)) { + return records; + } List elements = splitToRecords(response); for (Element record : elements) { records.add(transformSourceRecords(record)); @@ -416,6 +419,9 @@ private Integer countCiniiElement(String appId, Integer maxResult, String author Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java index 7dde330b27ec..5c4c49deaec9 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/crossref/CrossRefImportMetadataSourceServiceImpl.java @@ -158,6 +158,9 @@ public List call() throws Exception { } Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(response)) { + return results; + } JsonNode jsonNode = convertStringJsonToJsonNode(response); Iterator nodes = jsonNode.at("/message/items").iterator(); while (nodes.hasNext()) { @@ -194,6 +197,9 @@ public List call() throws Exception { URIBuilder uriBuilder = new URIBuilder(url + "/" + ID); Map> params = new HashMap>(); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(responseString)) { + return results; + } JsonNode jsonNode = convertStringJsonToJsonNode(responseString); JsonNode messageNode = jsonNode.at("/message"); results.add(transformSourceRecords(messageNode.toString())); @@ -246,6 +252,9 @@ public List call() throws Exception { } Map> params = new HashMap>(); String resp = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(resp)) { + return results; + } JsonNode jsonNode = convertStringJsonToJsonNode(resp); Iterator nodes = jsonNode.at("/message/items").iterator(); while (nodes.hasNext()) { @@ -284,6 +293,9 @@ public Integer call() throws Exception { uriBuilder.addParameter("query", query.getParameterAsClass("query", String.class)); Map> params = new HashMap>(); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(responseString)) { + return 0; + } JsonNode jsonNode = convertStringJsonToJsonNode(responseString); return jsonNode.at("/message/total-results").asInt(); } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java index 2ee114a3801a..77e34ba9e41b 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/epo/service/EpoImportMetadataSourceServiceImpl.java @@ -148,6 +148,9 @@ protected String login() throws IOException, HttpException { Map> params = getLoginParams(); String entity = "grant_type=client_credentials"; String json = liveImportClient.executeHttpPostRequest(this.authUrl, params, entity); + if (StringUtils.isBlank(json)) { + return json; + } ObjectMapper mapper = new ObjectMapper(new JsonFactory()); JsonNode rootNode = mapper.readTree(json); JsonNode accessTokenNode = rootNode.get("access_token"); @@ -191,7 +194,8 @@ public int getRecordsCount(Query query) throws MetadataSourceException { String bearer = login(); return retry(new CountRecordsCallable(query, bearer)); } catch (IOException | HttpException e) { - e.printStackTrace(); + log.warn(e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); } } return 0; @@ -205,7 +209,7 @@ public Collection getRecords(String query, int start, String bearer = login(); return retry(new SearchByQueryCallable(query, bearer, start, count)); } catch (IOException | HttpException e) { - log.warn(e.getMessage()); + log.warn(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e); } } @@ -248,14 +252,12 @@ public ImportRecord getRecord(Query query) throws MetadataSourceException { } @Override - public Collection findMatchingRecords(Item item) - throws MetadataSourceException { + public Collection findMatchingRecords(Item item) throws MetadataSourceException { return null; } @Override - public Collection findMatchingRecords(Query query) - throws MetadataSourceException { + public Collection findMatchingRecords(Query query) throws MetadataSourceException { return null; } @@ -377,7 +379,7 @@ public List call() throws Exception { private Integer countDocument(String bearer, String query) { if (StringUtils.isBlank(bearer)) { - return null; + return 0; } try { Map> params = new HashMap>(); @@ -390,6 +392,9 @@ private Integer countDocument(String bearer, String query) { uriBuilder.addParameter("q", query); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isBlank(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); @@ -404,7 +409,7 @@ private Integer countDocument(String bearer, String query) { return Integer.parseInt(totalRes); } catch (JDOMException | IOException | URISyntaxException | JaxenException e) { log.error(e.getMessage(), e); - return null; + return 0; } } @@ -427,6 +432,9 @@ private List searchDocumentIds(String bearer, String query, int s uriBuilder.addParameter("q", query); String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isBlank(response)) { + return results; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); @@ -467,6 +475,9 @@ private List searchDocument(String bearer, String id, String docTy String url = this.url.replace("$(doctype)", docType).replace("$(id)", id); String response = liveImportClient.executeHttpGetRequest(1000, url, params); + if (StringUtils.isBlank(response)) { + return results; + } List elements = splitToRecords(response); for (Element element : elements) { results.add(transformSourceRecords(element)); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java index 1ec0da74206e..ba95e1e9045a 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/pubmedeurope/PubmedEuropeMetadataSourceServiceImpl.java @@ -292,6 +292,9 @@ public Integer count(String query) throws URISyntaxException, ClientProtocolExce try { Map> params = new HashMap>(); String response = liveImportClient.executeHttpGetRequest(1000, buildURI(1, query), params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java index d0c2fb078a2c..36cf8ae2dc49 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/scopus/service/ScopusImportMetadataSourceServiceImpl.java @@ -200,6 +200,9 @@ public Integer call() throws Exception { Map requestParams = getRequestParameters(query, null, null, null); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); @@ -243,6 +246,9 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, null, null); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -302,6 +308,9 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, start, count); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); @@ -347,6 +356,9 @@ public List call() throws Exception { Map requestParams = getRequestParameters(queryString, viewMode, start, count); params.put(URI_PARAMETERS, requestParams); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } List elements = splitToRecords(response); for (Element record : elements) { results.add(transformSourceRecords(record)); diff --git a/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java index a4f90fa5ba61..1b942a7f1525 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/vufind/VuFindImportMetadataSourceServiceImpl.java @@ -144,6 +144,9 @@ public Integer call() throws Exception { uriBuilder.addParameter("lookfor", query.getParameterAsClass("query", String.class)); Map> params = new HashMap>(); String responseString = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); + if (StringUtils.isEmpty(responseString)) { + return 0; + } JsonNode node = convertStringJsonToJsonNode(responseString); JsonNode resultCountNode = node.get("resultCount"); return resultCountNode.intValue(); @@ -182,8 +185,7 @@ public String call() throws Exception { } } Map> params = new HashMap>(); - String response = liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); - return response; + return liveImportClient.executeHttpGetRequest(1000, uriBuilder.toString(), params); } } @@ -309,6 +311,9 @@ private JsonNode convertStringJsonToJsonNode(String json) { private List extractMetadataFromRecordList(String records) { List recordsResult = new ArrayList<>(); + if (StringUtils.isEmpty(records)) { + return recordsResult; + } JsonNode jsonNode = convertStringJsonToJsonNode(records); JsonNode node = jsonNode.get("records"); if (Objects.nonNull(node) && node.isArray()) { diff --git a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java index 2ccdc12b8db2..b669cd860078 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/wos/service/WOSImportMetadataSourceServiceImpl.java @@ -145,6 +145,9 @@ public Integer call() throws Exception { Map> params = new HashMap>(); params.put(HEADER_PARAMETERS, getRequestParameters()); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return 0; + } SAXBuilder saxBuilder = new SAXBuilder(); Document document = saxBuilder.build(new StringReader(response)); @@ -179,6 +182,9 @@ public List call() throws Exception { Map> params = new HashMap>(); params.put(HEADER_PARAMETERS, getRequestParameters()); String response = liveImportClient.executeHttpGetRequest(timeout, urlString, params); + if (StringUtils.isEmpty(response)) { + return results; + } List elements = splitToRecords(response); for (Element record : elements) { @@ -226,6 +232,9 @@ public List call() throws Exception { String url = urlSearch + URLEncoder.encode(queryString, StandardCharsets.UTF_8) + "&count=" + count + "&firstRecord=" + (start + 1); String response = liveImportClient.executeHttpGetRequest(timeout, url, params); + if (StringUtils.isEmpty(response)) { + return results; + } List omElements = splitToRecords(response); for (Element el : omElements) { From 4030fe1ff62a6fed1d88d852a77247cfb385b123 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Mon, 3 Jul 2023 17:44:43 +0200 Subject: [PATCH 373/394] [DSC-1092] update mapping for EPO --- dspace/config/spring/api/epo-integration.xml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dspace/config/spring/api/epo-integration.xml b/dspace/config/spring/api/epo-integration.xml index ef4ac5fc867e..b8b737ba80b8 100644 --- a/dspace/config/spring/api/epo-integration.xml +++ b/dspace/config/spring/api/epo-integration.xml @@ -60,7 +60,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -97,10 +97,9 @@ - + - - + From 2d8c236a2be41a5fce0ab9ee3fe3c5ff2469ce90 Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Wed, 5 Jul 2023 11:39:07 +0200 Subject: [PATCH 374/394] [DSC-1155] fix configurations for ads and vufind external providers --- dspace/config/local.cfg.EXAMPLE | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 74ad5d5ad33a..263520fb9e29 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -275,19 +275,19 @@ metadata.extraction.grobid.url = # LOOKUP SUBMISSION PROVIDER CONFIGURATION # ############################################ # In order to use the Astrophysics Data System services you need to obtain an API Key from Astrophysics Data System. Once you get it, add it to the following configuration value. -# Note that when apikey is configured by default the service is enabled, see bte.xml for further configuration -submission.lookup.ads.apikey = +# Note that when apikey is configured by default the service is enabled, see spring-dspace-addon-import-services.xml for further configuration +ads.key = # In order to use the EPO services you need to obtain an API Key from https://developers.epo.org. Once you get it, add it to the # following configuration value. -# Note that when apikey is configured by default the service is enabled, see bte.xml for further configuration +# Note that when apikey is configured by default the service is enabled, see spring-dspace-addon-import-services.xml for further configuration epo.consumerKey= epo.consumerSecretKey= # VuFind endpoints (i.e. https://vufind.org/demo/api/v1/...) # please note that VuFind API are IP protected so you need to configure it to grant access from the DSpace Server IP -vufind.api-search = -vufind.api-record = +vufind.url.search = +vufind.url = ########################################## # ORCID Integration # From 0984fbe782efb419a8c78e700abf87a47905bbcd Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Wed, 5 Jul 2023 11:50:28 +0200 Subject: [PATCH 375/394] [DSC-1006] update the scopus fetch size to 20 by default --- dspace/config/modules/external-providers.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/modules/external-providers.cfg b/dspace/config/modules/external-providers.cfg index 863f49001f64..fbd6f406c14d 100644 --- a/dspace/config/modules/external-providers.cfg +++ b/dspace/config/modules/external-providers.cfg @@ -78,7 +78,7 @@ scopus.instToken = # by default we use standart mode, to use complete mode this variable must be valued with 'COMPLETE' scopus.search-api.viewMode = # Number of items to fetch within same request using OR clause concatenation. -scopus.fetchSize = 1 +scopus.fetchSize = 20 ################################################################# #------------------- Web of Science (WOS) ----------------------# #---------------------------------------------------------------# From d2a378b6bb412a7120ff8106a1b192bb4b46f285 Mon Sep 17 00:00:00 2001 From: Francesco Pio Scognamiglio Date: Thu, 6 Jul 2023 11:30:59 +0200 Subject: [PATCH 376/394] [DSC-1158] avoid NPE on the population of authors during scopus import --- .../contributor/AuthorMetadataContributor.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java index c28c229188a7..b2d2ea22b0c6 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/AuthorMetadataContributor.java @@ -111,10 +111,16 @@ private List getMetadataOfAuthors(Element element) throws JaxenExc addMetadatum(metadatums, getMetadata(getElementValue(surname) + ", " + getElementValue(givenName), this.authname)); - addMetadatum(metadatums, getMetadata(getElementValue(scopusId), this.scopusId)); - addMetadatum(metadatums, getMetadata(getElementValue(orcid), this.orcid)); - addMetadatum(metadatums, getMetadata(StringUtils.isNotBlank(afid.getValue()) - ? this.affId2affName.get(afid.getValue()) : null, this.affiliation)); + if (this.scopusId != null) { + addMetadatum(metadatums, getMetadata(getElementValue(scopusId), this.scopusId)); + } + if (this.orcid != null) { + addMetadatum(metadatums, getMetadata(getElementValue(orcid), this.orcid)); + } + if (this.affiliation != null) { + addMetadatum(metadatums, getMetadata(StringUtils.isNotBlank(afid.getValue()) + ? this.affId2affName.get(afid.getValue()) : null, this.affiliation)); + } return metadatums; } From 0bb0c854de1628437563e940887307033adc0c9d Mon Sep 17 00:00:00 2001 From: "yevhenii.lohatskyi" Date: Wed, 5 Jul 2023 16:30:28 +0300 Subject: [PATCH 377/394] [DSC-1150] add dspace.legacy.oai-identifier metadata to dspace-types.xml --- dspace/config/registries/dspace-types.xml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/dspace/config/registries/dspace-types.xml b/dspace/config/registries/dspace-types.xml index eb71b9edd9d6..d768ff41361f 100644 --- a/dspace/config/registries/dspace-types.xml +++ b/dspace/config/registries/dspace-types.xml @@ -13,7 +13,7 @@ dspace process filetype - + @@ -105,7 +105,14 @@ dspace orcid webhook - + + + + + dspace + legacy + oai-identifier + From 5c0dc9d18e324ad2cb73c46100d42a5817d72eb3 Mon Sep 17 00:00:00 2001 From: "yevhenii.lohatskyi" Date: Wed, 5 Jul 2023 16:39:55 +0300 Subject: [PATCH 378/394] [DSC-1150] set handle to legacyOaiId if it's present and refactor --- .../main/java/org/dspace/xoai/app/XOAI.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index e27a3ee947cb..2db53bff9be5 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -36,6 +36,7 @@ import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; @@ -81,7 +82,7 @@ */ @SuppressWarnings("deprecation") public class XOAI { - private static Logger log = LogManager.getLogger(XOAI.class); + private static final Logger log = LogManager.getLogger(XOAI.class); // needed because the solr query only returns 10 rows by default private final Context context; @@ -104,7 +105,7 @@ public class XOAI { private final static ConfigurationService configurationService = DSpaceServicesFactory.getInstance() .getConfigurationService(); - private List extensionPlugins; + private final List extensionPlugins; private List getFileFormats(Item item) { List formats = new ArrayList<>(); @@ -151,9 +152,9 @@ private void println(String line) { } public int index() throws DSpaceSolrIndexerException { - int result = 0; - try { + int result; + try { if (clean) { clearIndex(); System.out.println("Using full import."); @@ -169,8 +170,8 @@ public int index() throws DSpaceSolrIndexerException { } else { result = this.index((Date) results.get(0).getFieldValue("item.lastmodified")); } - } + solrServerResolver.getServer().commit(); if (optimize) { @@ -214,7 +215,7 @@ private int index(Date last) throws DSpaceSolrIndexerException, IOException { * @param last maximum date for an item to be considered for an update * @return Iterator over list of items which might have changed their visibility * since the last update. - * @throws DSpaceSolrIndexerException + * @throws DSpaceSolrIndexerException e */ private Iterator getItemsWithPossibleChangesBefore(Date last) throws DSpaceSolrIndexerException, IOException { try { @@ -365,7 +366,7 @@ private int index(Iterator iterator) throws DSpaceSolrIndexerException { * * @param item Item * @return date - * @throws SQLException + * @throws SQLException e */ private Date getMostRecentModificationDate(Item item) throws SQLException { List dates = new LinkedList<>(); @@ -398,7 +399,8 @@ private SolrInputDocument index(Item item) SolrInputDocument doc = new SolrInputDocument(); doc.addField("item.id", item.getID().toString()); - String handle = item.getHandle(); + String legacyOaiId = itemService.getMetadataFirstValue(item, "dspace", "legacy", "oai-identifier", Item.ANY); + String handle = StringUtils.isNotEmpty(legacyOaiId) ? legacyOaiId.split(":")[2] : item.getHandle(); doc.addField("item.handle", handle); boolean isEmbargoed = !this.isPublic(item); @@ -418,7 +420,7 @@ private SolrInputDocument index(Item item) * future will be marked as such. */ - boolean isPublic = isEmbargoed ? (isIndexed ? isCurrentlyVisible : false) : true; + boolean isPublic = !isEmbargoed || (isIndexed && isCurrentlyVisible); doc.addField("item.public", isPublic); // if the visibility of the item will change in the future due to an @@ -433,8 +435,7 @@ private SolrInputDocument index(Item item) * because this will override the item.public flag. */ - doc.addField("item.deleted", - (item.isWithdrawn() || !item.isDiscoverable() || (isEmbargoed ? isPublic : false))); + doc.addField("item.deleted", (item.isWithdrawn() || !item.isDiscoverable() || (isEmbargoed && isPublic))); /* * An item that is embargoed will potentially not be harvested by incremental @@ -574,8 +575,8 @@ private static void cleanCache(XOAIItemCacheService xoaiItemCacheService, XOAICa public static void main(String[] argv) throws IOException, ConfigurationException { - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext( - new Class[] { BasicConfiguration.class }); + AnnotationConfigApplicationContext applicationContext = + new AnnotationConfigApplicationContext(BasicConfiguration.class); XOAICacheService cacheService = applicationContext.getBean(XOAICacheService.class); XOAIItemCacheService itemCacheService = applicationContext.getBean(XOAIItemCacheService.class); @@ -596,10 +597,9 @@ public static void main(String[] argv) throws IOException, ConfigurationExceptio String[] validDatabaseCommands = { COMMAND_CLEAN_CACHE, COMMAND_COMPILE_ITEMS, COMMAND_ERASE_COMPILED_ITEMS }; - boolean solr = true; // Assuming solr by default - solr = !("database").equals(configurationService.getProperty("oai.storage", "solr")); - + boolean solr = !("database").equals(configurationService.getProperty("oai.storage", "solr")); boolean run = false; + if (line.getArgs().length > 0) { if (solr) { if (Arrays.asList(validSolrCommands).contains(line.getArgs()[0])) { From 8803bc9d6c06bfea8a967ffa89f381ac6160448c Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Fri, 14 Jul 2023 15:36:49 +0200 Subject: [PATCH 379/394] [CST-10940] correction request does not remove bitstreams --- .../versioning/ItemCorrectionProvider.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java index 75efd7ae6965..a65ac8af97e1 100644 --- a/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java +++ b/dspace-api/src/main/java/org/dspace/versioning/ItemCorrectionProvider.java @@ -193,8 +193,7 @@ protected void updateBundlesAndBitstreams(Context c, Item itemNew, Item nativeIt List nativeBundles = nativeItem.getBundles(bundleName); List correctedBundles = itemNew.getBundles(bundleName); - if (CollectionUtils.isEmpty(nativeBundles) && CollectionUtils.isEmpty(correctedBundles) || - CollectionUtils.isEmpty(correctedBundles)) { + if (CollectionUtils.isEmpty(nativeBundles) && CollectionUtils.isEmpty(correctedBundles)) { continue; } @@ -205,7 +204,14 @@ protected void updateBundlesAndBitstreams(Context c, Item itemNew, Item nativeIt nativeBundle = nativeBundles.get(0); } - updateBundleAndBitstreams(c, nativeBundle, correctedBundles.get(0)); + Bundle correctedBundle; + if (CollectionUtils.isEmpty(correctedBundles)) { + correctedBundle = bundleService.create(c, nativeItem, bundleName); + } else { + correctedBundle = correctedBundles.get(0); + } + + updateBundleAndBitstreams(c, nativeBundle, correctedBundle); } } @@ -255,7 +261,26 @@ protected void updateBundleAndBitstreams(Context c, Bundle nativeBundle, Bundle } } + deleteBitstreams(nativeBundle, correctedBundle); bundleService.update(c, nativeBundle); + if (nativeBundle.getItems().isEmpty()) { + bundleService.delete(c, nativeBundle); + } + } + + private void deleteBitstreams(Bundle nativeBundle, Bundle correctedBundle) { + for (Bitstream bitstream : nativeBundle.getBitstreams()) { + if (contains(correctedBundle, bitstream)) { + continue; + } + nativeBundle.removeBitstream(bitstream); + } + } + + private boolean contains(Bundle bundle, Bitstream bitstream) { + return bundle.getBitstreams().stream() + .map(Bitstream::getChecksum) + .anyMatch(cs -> bitstream.getChecksum().equals(cs)); } protected Bitstream findBitstreamByChecksum(Bundle bundle, String bitstreamChecksum) { From efc5bcd52ab0c4c403aa5eec1b7cc0d38f3f1371 Mon Sep 17 00:00:00 2001 From: eskander Date: Tue, 18 Jul 2023 13:39:26 +0300 Subject: [PATCH 380/394] [DSC-1166] - Configured Download statistics - added new four statistics (collection/community-itemRepertos and collection/community-downloadRepertos) - handled failed test methos - added new ITs test methods --- .../content/StatisticsDatasetDisplay.java | 9 +- .../AbstractTopSolrStatsFieldGenerator.java | 4 +- .../statistics/TopCategoriesGenerator.java | 3 +- .../rest/statistics/TopItemsGenerator.java | 5 +- .../TotalDownloadsAndVisitsGenerator.java | 5 +- .../rest/statistics/TotalVisitGenerator.java | 2 +- .../TotalVisitPerPeriodGenerator.java | 3 +- .../app/rest/utils/UsageReportUtils.java | 6 + .../app/rest/StatisticsRestRepositoryIT.java | 531 +++++++++++++++++- dspace/config/spring/api/discovery.xml | 26 + dspace/config/spring/rest/statistics.xml | 234 ++++++++ 11 files changed, 807 insertions(+), 21 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDatasetDisplay.java b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDatasetDisplay.java index 941d4a33d314..75e18ec49e1f 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDatasetDisplay.java +++ b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDatasetDisplay.java @@ -278,9 +278,14 @@ public String composeFilterQuery(String startDate, String endDate, boolean relat } //Creates query for usage raport generator - public String composeQueryWithInverseRelation(DSpaceObject dSpaceObject, List default_queries ) { + public String composeQueryWithInverseRelation(DSpaceObject dSpaceObject, List default_queries, int type) { StringBuilder query = new StringBuilder(); - query.append("{!join from=search.resourceid to=id fromIndex="); + if (type == Constants.BITSTREAM) { + query.append("{!join from=search.resourceid to=owningItem fromIndex="); + } else { + query.append("{!join from=search.resourceid to=id fromIndex="); + } + query.append(configurationService.getProperty("solr.multicorePrefix")); query.append("search} "); boolean isFirstDefaultQuery = true; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/AbstractTopSolrStatsFieldGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/AbstractTopSolrStatsFieldGenerator.java index 9d04803c71ea..6d8242ca9fb1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/AbstractTopSolrStatsFieldGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/AbstractTopSolrStatsFieldGenerator.java @@ -66,8 +66,8 @@ Dataset getTypeStatsDataset(Context context, DSpaceObject dso, String typeAxisSt } else { hasValidRelation = true; - query = statisticsDatasetDisplay - .composeQueryWithInverseRelation(dso, discoveryConfiguration.getDefaultFilterQueries()); + query = statisticsDatasetDisplay.composeQueryWithInverseRelation( + dso, discoveryConfiguration.getDefaultFilterQueries(), getDsoType(dso)); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopCategoriesGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopCategoriesGenerator.java index 58532e46bf0f..39d8a1730c06 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopCategoriesGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopCategoriesGenerator.java @@ -93,7 +93,8 @@ private int getCategoryCount(DSpaceObject dso, DiscoveryConfiguration discoveryC private String composeCategoryQuery(DSpaceObject dso, DiscoveryConfiguration configuration, String categoryQuery) { List defaultFilterQueries = configuration.getDefaultFilterQueries(); - String query = new StatisticsDatasetDisplay().composeQueryWithInverseRelation(dso, defaultFilterQueries); + String query = new StatisticsDatasetDisplay().composeQueryWithInverseRelation(dso, + defaultFilterQueries, dso.getType()); if (categoryQuery.equals(OTHER_CATEGORY)) { return query + " AND " + getAllCategoryQueriesReverted(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopItemsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopItemsGenerator.java index d0805c68de36..1e67b0cac66c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopItemsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TopItemsGenerator.java @@ -81,9 +81,8 @@ public UsageReportRest createUsageReport(Context context, DSpaceObject root, Str } else { hasValidRelation = true; - query = statisticsDatasetDisplay - .composeQueryWithInverseRelation(root, - discoveryConfiguration.getDefaultFilterQueries()); + query = statisticsDatasetDisplay.composeQueryWithInverseRelation(root, + discoveryConfiguration.getDefaultFilterQueries(), getDsoType()); } } if (!hasValidRelation) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalDownloadsAndVisitsGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalDownloadsAndVisitsGenerator.java index 2a1d95e0c291..620333ce61b9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalDownloadsAndVisitsGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalDownloadsAndVisitsGenerator.java @@ -59,9 +59,8 @@ public UsageReportRest createUsageReport(Context context, DSpaceObject dso, Stri } else { hasValidRelation = true; - query = statisticsDatasetDisplay. - composeQueryWithInverseRelation( - dso, discoveryConfiguration.getDefaultFilterQueries()); + query = statisticsDatasetDisplay.composeQueryWithInverseRelation(dso, + discoveryConfiguration.getDefaultFilterQueries(), dso.getType()); } } if (!hasValidRelation) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitGenerator.java index 98ee393b5cd3..e419ac660fd6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitGenerator.java @@ -118,7 +118,7 @@ Dataset getDSOStatsDataset(Context context, DSpaceObject dso, int dsoType, Strin } else { hasValidRelation = true; query = statisticsDatasetDisplay.composeQueryWithInverseRelation( - dso, discoveryConfiguration.getDefaultFilterQueries()); + dso, discoveryConfiguration.getDefaultFilterQueries(), dso.getType()); type_of_dso = dso.getType(); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitPerPeriodGenerator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitPerPeriodGenerator.java index 5f7f53898abe..465f1022372a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitPerPeriodGenerator.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/statistics/TotalVisitPerPeriodGenerator.java @@ -97,7 +97,8 @@ public UsageReportRest createUsageReport(Context context, DSpaceObject dso, Stri statisticsDatasetDisplay .composeQueryWithInverseRelation( dso, - discoveryConfiguration.getDefaultFilterQueries() + discoveryConfiguration.getDefaultFilterQueries(), + getDsoType(dso) ) ); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java index 90f7ec252542..47dbcef1749d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java @@ -101,6 +101,12 @@ public class UsageReportUtils { public static final String TOP_DOWNLOAD_COUNTRIES_REPORT_ID = "TopDownloadsCountries"; public static final String TOP_DOWNLOAD_CITIES_REPORT_ID = "TopDownloadsCities"; public static final String TOTAL_DOWNLOAD_PER_MONTH_REPORT_ID = "TotalDownloadsPerMonth"; + public static final String TOP_ITEMS_CITIES_REPORT_ID = "TopItemsCities"; + public static final String TOP_ITEMS_CONTINENTS_REPORT_ID = "TopItemsContinents"; + public static final String TOP_ITEMS_COUNTRIES_REPORT_ID = "TopItemsCountries"; + public static final String TOP_ITEMS_CATEGORIES_REPORT_ID = "TopItemsCategories"; + public static final String TOTAL_ITEMS_VISITS_REPORT_ID = "TotalItemsVisits"; + public static final String TOTAL_ITEMS_VISITS_PER_MONTH_REPORT_ID = "TotalItemsVisitsPerMonth"; /** * Get list of usage reports that are applicable to the DSO (of given UUID) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java index 0f7996a765f3..68dee1555f72 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java @@ -25,24 +25,27 @@ import static org.dspace.app.rest.utils.UsageReportUtils.TOP_DOWNLOAD_CITIES_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOP_DOWNLOAD_CONTINENTS_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOP_DOWNLOAD_COUNTRIES_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_CATEGORIES_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_CITIES_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_CONTINENTS_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_COUNTRIES_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOP_ITEMS_REPORT_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_DOWNLOADS_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_DOWNLOADS_REPORT_ID_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_DOWNLOAD_PER_MONTH_REPORT_ID; -//import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_DOWNLOADS_REPORT_ID_RELATION_PERSON_RESEARCHOUTPUTS; +import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_ITEMS_VISITS_PER_MONTH_REPORT_ID; +import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_ITEMS_VISITS_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_PER_MONTH_REPORT_ID; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_PER_MONTH_REPORT_ID_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_PER_MONTH_REPORT_ID_RELATION_PERSON_PROJECTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_PER_MONTH_REPORT_ID_RELATION_PERSON_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID; -//import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID_RELATION_ORGUNIT_PROJECTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID_RELATION_PERSON_PROJECTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_REPORT_ID_RELATION_PERSON_RESEARCHOUTPUTS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_TOTAL_DOWNLOADS; import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_TOTAL_DOWNLOADS_RELATION_ORGUNIT_RP_RESEARCHOUTPUTS; -//import static org.dspace.app.rest.utils.UsageReportUtils.TOTAL_VISITS_TOTAL_DOWNLOADS_RELATION_PERSON_RESEARCHOUTPUTS; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -921,10 +924,10 @@ public void TotalDownloadsReport_Item_NotVisited() throws Exception { } @Test - public void TotalDownloadsReport_NotSupportedDSO_Collection() throws Exception { + public void TotalDownloadsReport_SupportedDSO_Collection() throws Exception { getClient(adminToken) .perform(get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) - .andExpect(status().isNotFound()); + .andExpect(status().isOk()); } /** @@ -1575,7 +1578,8 @@ public void usageReportsSearch_Community_Visited() throws Exception { // And request the community usage reports getClient(adminToken) - .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + + .perform(get("/api/statistics/usagereports/search/object?category=community-mainReports" + + "&uri=http://localhost:8080/server/api/core" + "/communities/" + communityVisited.getID())) // ** THEN ** .andExpect(status().isOk()) @@ -1616,7 +1620,8 @@ public void usageReportsSearch_Collection_NotVisited() throws Exception { // Collection is not visited // And request the collection's usage reports getClient(adminToken) - .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + + .perform(get("/api/statistics/usagereports/search/object?category=collection-mainReports" + + "&uri=http://localhost:8080/server/api/core" + "/collections/" + collectionNotVisited.getID())) // ** THEN ** .andExpect(status().isOk()) @@ -2035,7 +2040,8 @@ public void usageReportsSearch_Community_VisitedAtTime() throws Exception { String endDate = dateFormat.format(cal.getTime()); // And request the community usage reports getClient(adminToken) - .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + + .perform(get("/api/statistics/usagereports/search/object?category=community-mainReports" + + "&uri=http://localhost:8080/server/api/core" + "/communities/" + communityVisited.getID() + "&startDate=2019-06-01&endDate=" + endDate)) // ** THEN ** .andExpect(status().isOk()) @@ -2435,6 +2441,515 @@ public void usageReportsSearch_OrgUnitWithPublicationVisited() throws Exception ))); } + @Test + public void usageReportsSearch_Collection_ItemReports() throws Exception { + context.turnOffAuthorisationSystem(); + + Item item = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item") + .withType("Controlled Vocabulary for Resource Type Genres::image") + .build(); + Item item2 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 2") + .withType("Controlled Vocabulary for Resource Type Genres::thesis") + .build(); + Item item3 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 3") + .withType("Controlled Vocabulary for Resource Type Genres::thesis::bachelor thesis") + .build(); + Item item4 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 4") + .withType("Controlled Vocabulary for Resource Type Genres::text::periodical::" + + "journal::contribution to journal::journal article") + .build(); + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + + ViewEventRest viewEventRest = new ViewEventRest(); + viewEventRest.setTargetType("item"); + viewEventRest.setTargetId(item.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest2 = new ViewEventRest(); + viewEventRest2.setTargetType("item"); + viewEventRest2.setTargetId(item2.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest2)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest2)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest3 = new ViewEventRest(); + viewEventRest3.setTargetType("item"); + viewEventRest3.setTargetId(item3.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest3)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest4 = new ViewEventRest(); + viewEventRest4.setTargetType("item"); + viewEventRest4.setTargetId(item4.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest4)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + UsageReportPointDsoTotalVisitsRest expectedPoint1 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint1.addValue("views", 1); + expectedPoint1.setType("item"); + expectedPoint1.setLabel("My item"); + expectedPoint1.setId(item.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint2 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint2.addValue("views", 2); + expectedPoint2.setType("item"); + expectedPoint2.setLabel("My item 2"); + expectedPoint2.setId(item2.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint3 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint3.addValue("views", 1); + expectedPoint3.setType("item"); + expectedPoint3.setLabel("My item 3"); + expectedPoint3.setId(item3.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint4 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint4.addValue("views", 1); + expectedPoint4.setType("item"); + expectedPoint4.setLabel("My item 4"); + expectedPoint4.setId(item4.getID().toString()); + + List points = List.of(expectedPoint1, expectedPoint2, expectedPoint3, expectedPoint4); + + UsageReportPointCityRest pointCity = new UsageReportPointCityRest(); + pointCity.addValue("views", 5); + pointCity.setId("New York"); + + UsageReportPointContinentRest pointContinent = new UsageReportPointContinentRest(); + pointContinent.addValue("views", 5); + pointContinent.setId("North America"); + + UsageReportPointCountryRest pointCountry = new UsageReportPointCountryRest(); + pointCountry.addValue("views", 5); + pointCountry.setIdAndLabel(Locale.US.getCountry(), Locale.US.getDisplayCountry(context.getCurrentLocale())); + + UsageReportPointCategoryRest articleCategory = new UsageReportPointCategoryRest(); + articleCategory.addValue("views", 1); + articleCategory.setId("article"); + + UsageReportPointCategoryRest thesisCategory = new UsageReportPointCategoryRest(); + thesisCategory.addValue("views", 3); + thesisCategory.setId("thesis"); + + UsageReportPointCategoryRest otherCategory = new UsageReportPointCategoryRest(); + otherCategory.addValue("views", 1); + otherCategory.setId("other"); + + UsageReportPointCategoryRest bookCategory = new UsageReportPointCategoryRest(); + bookCategory.addValue("views", 0); + bookCategory.setId("book"); + + UsageReportPointCategoryRest bookChapterCategory = new UsageReportPointCategoryRest(); + bookChapterCategory.addValue("views", 0); + bookChapterCategory.setId("bookChapter"); + + UsageReportPointCategoryRest datasetCategory = new UsageReportPointCategoryRest(); + datasetCategory.addValue("views", 0); + datasetCategory.setId("dataset"); + + List categories = List.of(articleCategory, thesisCategory, otherCategory, bookCategory, + bookChapterCategory, datasetCategory); + + // And request the collections global usage report (show top most popular items) + getClient(adminToken) + .perform(get("/api/statistics/usagereports/search/object") + .param("category", "collection-itemReports") + .param("uri", "http://localhost:8080/server/api/core/collections/" + collectionNotVisited.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) + .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( + matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_ITEMS_VISITS_REPORT_ID, + TOP_ITEMS_REPORT_ID, points), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_ITEMS_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, List.of(pointCity)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_ITEMS_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, getLastMonthVisitPoints(5)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_ITEMS_CONTINENTS_REPORT_ID, + TOP_CONTINENTS_REPORT_ID, List.of(pointContinent)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_ITEMS_CATEGORIES_REPORT_ID, + TOP_CATEGORIES_REPORT_ID, categories), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_ITEMS_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, List.of(pointCountry))))); + } + + @Test + public void usageReportsSearch_Collection_DownloadReports() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item item1 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 1") + .build(); + + Item item2 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 2") + .build(); + + Item item3 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 3") + .build(); + + Bitstream bitstream1 = createBitstream(item1, "Bitstream 1"); + Bitstream bitstream2 = createBitstream(item1, "Bitstream 2"); + Bitstream bitstream3 = createBitstream(item2, "Bitstream 3"); + Bitstream bitstream4 = createBitstream(item3, "Bitstream 4"); + + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream4.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream4.getID() + "/content")) + .andExpect(status().isOk()); + + context.restoreAuthSystemState(); + + UsageReportPointDsoTotalVisitsRest expectedPoint1 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint1.addValue("views", 3); + expectedPoint1.setType("item"); + expectedPoint1.setLabel("Item 1"); + expectedPoint1.setId(item1.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint2 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint2.addValue("views", 3); + expectedPoint2.setType("item"); + expectedPoint2.setLabel("Item 2"); + expectedPoint2.setId(item2.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint3 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint3.addValue("views", 2); + expectedPoint3.setType("item"); + expectedPoint3.setLabel("Item 3"); + expectedPoint3.setId(item3.getID().toString()); + + List points = List.of(expectedPoint1, expectedPoint2, expectedPoint3); + + UsageReportPointCityRest pointCity = new UsageReportPointCityRest(); + pointCity.addValue("views", 8); + pointCity.setId("New York"); + + UsageReportPointContinentRest pointContinent = new UsageReportPointContinentRest(); + pointContinent.addValue("views", 8); + pointContinent.setId("North America"); + + UsageReportPointCountryRest pointCountry = new UsageReportPointCountryRest(); + pointCountry.addValue("views", 8); + pointCountry.setIdAndLabel(Locale.US.getCountry(), Locale.US.getDisplayCountry(context.getCurrentLocale())); + + getClient(adminToken) + .perform(get("/api/statistics/usagereports/search/object") + .param("category", "collection-downloadReports") + .param("uri", "http://localhost:8080/server/api/core/collections/" + collectionNotVisited.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) + .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( + matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOP_ITEMS_REPORT_ID, points), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_DOWNLOAD_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, List.of(pointCity)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_DOWNLOAD_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, getLastMonthVisitPoints(8)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_DOWNLOAD_CONTINENTS_REPORT_ID, + TOP_CONTINENTS_REPORT_ID, List.of(pointContinent)), + matchUsageReport(collectionNotVisited.getID() + "_" + TOP_DOWNLOAD_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, List.of(pointCountry))))); + } + + @Test + public void usageReportsSearch_Community_ItemReports() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context).build(); + collectionNotVisited = CollectionBuilder.createCollection(context, community).build(); + + Item item = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item") + .withType("Controlled Vocabulary for Resource Type Genres::image") + .build(); + Item item2 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 2") + .withType("Controlled Vocabulary for Resource Type Genres::thesis") + .build(); + Item item3 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 3") + .withType("Controlled Vocabulary for Resource Type Genres::thesis::bachelor thesis") + .build(); + Item item4 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("My item 4") + .withType("Controlled Vocabulary for Resource Type Genres::text::periodical::" + + "journal::contribution to journal::journal article") + .build(); + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + + ViewEventRest viewEventRest = new ViewEventRest(); + viewEventRest.setTargetType("item"); + viewEventRest.setTargetId(item.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest2 = new ViewEventRest(); + viewEventRest2.setTargetType("item"); + viewEventRest2.setTargetId(item2.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest2)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest2)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest3 = new ViewEventRest(); + viewEventRest3.setTargetType("item"); + viewEventRest3.setTargetId(item3.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest3)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + ViewEventRest viewEventRest4 = new ViewEventRest(); + viewEventRest4.setTargetType("item"); + viewEventRest4.setTargetId(item4.getID()); + + getClient().perform(post("/api/statistics/viewevents") + .content(mapper.writeValueAsBytes(viewEventRest4)) + .contentType(contentType)) + .andExpect(status().isCreated()); + + UsageReportPointDsoTotalVisitsRest expectedPoint1 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint1.addValue("views", 1); + expectedPoint1.setType("item"); + expectedPoint1.setLabel("My item"); + expectedPoint1.setId(item.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint2 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint2.addValue("views", 2); + expectedPoint2.setType("item"); + expectedPoint2.setLabel("My item 2"); + expectedPoint2.setId(item2.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint3 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint3.addValue("views", 1); + expectedPoint3.setType("item"); + expectedPoint3.setLabel("My item 3"); + expectedPoint3.setId(item3.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint4 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint4.addValue("views", 1); + expectedPoint4.setType("item"); + expectedPoint4.setLabel("My item 4"); + expectedPoint4.setId(item4.getID().toString()); + + List points = List.of(expectedPoint1, expectedPoint2, expectedPoint3, expectedPoint4); + + UsageReportPointCityRest pointCity = new UsageReportPointCityRest(); + pointCity.addValue("views", 5); + pointCity.setId("New York"); + + UsageReportPointContinentRest pointContinent = new UsageReportPointContinentRest(); + pointContinent.addValue("views", 5); + pointContinent.setId("North America"); + + UsageReportPointCountryRest pointCountry = new UsageReportPointCountryRest(); + pointCountry.addValue("views", 5); + pointCountry.setIdAndLabel(Locale.US.getCountry(), Locale.US.getDisplayCountry(context.getCurrentLocale())); + + UsageReportPointCategoryRest articleCategory = new UsageReportPointCategoryRest(); + articleCategory.addValue("views", 1); + articleCategory.setId("article"); + + UsageReportPointCategoryRest thesisCategory = new UsageReportPointCategoryRest(); + thesisCategory.addValue("views", 3); + thesisCategory.setId("thesis"); + + UsageReportPointCategoryRest otherCategory = new UsageReportPointCategoryRest(); + otherCategory.addValue("views", 1); + otherCategory.setId("other"); + + UsageReportPointCategoryRest bookCategory = new UsageReportPointCategoryRest(); + bookCategory.addValue("views", 0); + bookCategory.setId("book"); + + UsageReportPointCategoryRest bookChapterCategory = new UsageReportPointCategoryRest(); + bookChapterCategory.addValue("views", 0); + bookChapterCategory.setId("bookChapter"); + + UsageReportPointCategoryRest datasetCategory = new UsageReportPointCategoryRest(); + datasetCategory.addValue("views", 0); + datasetCategory.setId("dataset"); + + List categories = List.of(articleCategory, thesisCategory, otherCategory, bookCategory, + bookChapterCategory, datasetCategory); + + // And request the collections global usage report (show top most popular items) + getClient(adminToken) + .perform(get("/api/statistics/usagereports/search/object") + .param("category", "community-itemReports") + .param("uri", "http://localhost:8080/server/api/core/communities/" + community.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) + .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( + matchUsageReport(community.getID() + "_" + TOTAL_ITEMS_VISITS_REPORT_ID, + TOP_ITEMS_REPORT_ID, points), + matchUsageReport(community.getID() + "_" + TOP_ITEMS_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, List.of(pointCity)), + matchUsageReport(community.getID() + "_" + TOTAL_ITEMS_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, getLastMonthVisitPoints(5)), + matchUsageReport(community.getID() + "_" + TOP_ITEMS_CONTINENTS_REPORT_ID, + TOP_CONTINENTS_REPORT_ID, List.of(pointContinent)), + matchUsageReport(community.getID() + "_" + TOP_ITEMS_CATEGORIES_REPORT_ID, + TOP_CATEGORIES_REPORT_ID, categories), + matchUsageReport(community.getID() + "_" + TOP_ITEMS_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, List.of(pointCountry))))); + } + + @Test + public void usageReportsSearch_Community_DownloadReports() throws Exception { + + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context).build(); + collectionNotVisited = CollectionBuilder.createCollection(context, community).build(); + + Item item1 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 1") + .build(); + + Item item2 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 2") + .build(); + + Item item3 = ItemBuilder.createItem(context, collectionNotVisited) + .withTitle("Item 3") + .build(); + + Bitstream bitstream1 = createBitstream(item1, "Bitstream 1"); + Bitstream bitstream2 = createBitstream(item1, "Bitstream 2"); + Bitstream bitstream3 = createBitstream(item2, "Bitstream 3"); + Bitstream bitstream4 = createBitstream(item3, "Bitstream 4"); + + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream1.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream2.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream3.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream4.getID() + "/content")) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bitstreams/" + bitstream4.getID() + "/content")) + .andExpect(status().isOk()); + + context.restoreAuthSystemState(); + + UsageReportPointDsoTotalVisitsRest expectedPoint1 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint1.addValue("views", 3); + expectedPoint1.setType("item"); + expectedPoint1.setLabel("Item 1"); + expectedPoint1.setId(item1.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint2 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint2.addValue("views", 3); + expectedPoint2.setType("item"); + expectedPoint2.setLabel("Item 2"); + expectedPoint2.setId(item2.getID().toString()); + + UsageReportPointDsoTotalVisitsRest expectedPoint3 = new UsageReportPointDsoTotalVisitsRest(); + expectedPoint3.addValue("views", 2); + expectedPoint3.setType("item"); + expectedPoint3.setLabel("Item 3"); + expectedPoint3.setId(item3.getID().toString()); + + List points = List.of(expectedPoint1, expectedPoint2, expectedPoint3); + + UsageReportPointCityRest pointCity = new UsageReportPointCityRest(); + pointCity.addValue("views", 8); + pointCity.setId("New York"); + + UsageReportPointContinentRest pointContinent = new UsageReportPointContinentRest(); + pointContinent.addValue("views", 8); + pointContinent.setId("North America"); + + UsageReportPointCountryRest pointCountry = new UsageReportPointCountryRest(); + pointCountry.addValue("views", 8); + pointCountry.setIdAndLabel(Locale.US.getCountry(), Locale.US.getDisplayCountry(context.getCurrentLocale())); + + getClient(adminToken) + .perform(get("/api/statistics/usagereports/search/object") + .param("category", "community-downloadReports") + .param("uri", "http://localhost:8080/server/api/core/communities/" + community.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) + .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( + matchUsageReport(community.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, TOP_ITEMS_REPORT_ID, points), + matchUsageReport(community.getID() + "_" + TOP_DOWNLOAD_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, List.of(pointCity)), + matchUsageReport(community.getID() + "_" + TOTAL_DOWNLOAD_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, getLastMonthVisitPoints(8)), + matchUsageReport(community.getID() + "_" + TOP_DOWNLOAD_CONTINENTS_REPORT_ID, + TOP_CONTINENTS_REPORT_ID, List.of(pointContinent)), + matchUsageReport(community.getID() + "_" + TOP_DOWNLOAD_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, List.of(pointCountry))))); + } + private List getLastMonthVisitPoints(int viewsLastMonth) { return getListOfVisitsPerMonthsPoints(viewsLastMonth, 0); } diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 1ddf0da887e9..6e759f05295b 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -133,6 +133,10 @@ + + + + @@ -2291,6 +2295,28 @@ + + + + + + + location.coll:{0} + + + + + + + + + + + location.comm:{0} + + + + diff --git a/dspace/config/spring/rest/statistics.xml b/dspace/config/spring/rest/statistics.xml index 91a970210d34..23b528eddcb5 100644 --- a/dspace/config/spring/rest/statistics.xml +++ b/dspace/config/spring/rest/statistics.xml @@ -417,6 +417,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -598,6 +774,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -610,6 +813,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -649,11 +879,15 @@ + + + + From dacaf5f0714fa6b2a18253602bea908f2316b1bd Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Tue, 18 Jul 2023 14:36:13 +0200 Subject: [PATCH 381/394] [DSC-1117] allow bitstream retrieval with custom policies --- .../java/org/dspace/app/rest/Application.java | 27 +++++++++++++++++++ .../app/rest/utils/ApplicationConfig.java | 27 +++++++++++++++++++ dspace/config/local.cfg.EXAMPLE | 2 ++ dspace/config/modules/rest.cfg | 5 ++++ 4 files changed, 61 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java index c85efed835bf..1c90a2e3e4f9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java @@ -12,6 +12,7 @@ import java.util.List; import javax.servlet.Filter; +import org.apache.commons.lang3.ArrayUtils; import org.dspace.app.rest.filter.DSpaceRequestContextFilter; import org.dspace.app.rest.model.hateoas.DSpaceLinkRelationProvider; import org.dspace.app.rest.parameter.resolver.SearchFilterResolver; @@ -187,9 +188,35 @@ public void addCorsMappings(@NonNull CorsRegistry registry) { .getCorsAllowedOrigins(configuration.getCorsAllowedOriginsConfig()); String[] iiifAllowedOrigins = configuration .getCorsAllowedOrigins(configuration.getIiifAllowedOriginsConfig()); + String[] bitstreamAllowedOrigins = configuration + .getCorsAllowedOrigins(configuration.getBitstreamAllowedOriginsConfig()); boolean corsAllowCredentials = configuration.getCorsAllowCredentials(); boolean iiifAllowCredentials = configuration.getIiifAllowCredentials(); + boolean bitstreamAllowCredentials = configuration.getBitstreamsAllowCredentials(); + + if (!ArrayUtils.isEmpty(bitstreamAllowedOrigins)) { + registry.addMapping("/api/core/bitstreams/**").allowedMethods(CorsConfiguration.ALL) + // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid + // for our Access-Control-Allow-Origin header + .allowCredentials(bitstreamAllowCredentials).allowedOrigins(bitstreamAllowedOrigins) + // Allow list of request preflight headers allowed to be sent to us from the client + .allowedHeaders("Accept", "Authorization", "Content-Type", "Origin", "X-On-Behalf-Of", + "X-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER", + "x-recaptcha-token", "Access-Control-Allow-Origin") + // Allow list of response headers allowed to be sent by us (the server) to the client + .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); + } else { + registry.addMapping("/api/core/bitstreams/**").allowedMethods(CorsConfiguration.ALL) + // Allow all orgin patterns by default + .allowedOriginPatterns(CorsConfiguration.ALL) + // Allow list of request preflight headers allowed to be sent to us from the client + .allowedHeaders("Accept", "Authorization", "Content-Type", "Origin", "X-On-Behalf-Of", + "X-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER", + "x-recaptcha-token") + // Allow list of response headers allowed to be sent by us (the server) to the client + .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); + } if (corsAllowedOrigins != null) { registry.addMapping("/api/**").allowedMethods(CorsConfiguration.ALL) // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java index 4b2dddd6b707..38feba0bc1d3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java @@ -36,6 +36,11 @@ public class ApplicationConfig { @Value("${iiif.cors.allowed-origins}") private String[] iiifCorsAllowedOrigins; + // Allowed IIIF CORS origins ("Access-Control-Allow-Origin" header) + // Can be overridden in DSpace configuration + @Value("${bitstream.cors.allowed-origins}") + private String[] bitstreamCorsAllowedOrigins; + // Whether to allow credentials (cookies) in CORS requests ("Access-Control-Allow-Credentials" header) // Defaults to true. Can be overridden in DSpace configuration @Value("${rest.cors.allow-credentials:true}") @@ -46,6 +51,11 @@ public class ApplicationConfig { @Value("${iiif.cors.allow-credentials:true}") private boolean iiifCorsAllowCredentials; + // Whether to allow credentials (cookies) in CORS requests ("Access-Control-Allow-Credentials" header) + // Defaults to true. Can be overridden in DSpace configuration + @Value("${bitstream.cors.allow-credentials:true}") + private boolean bitstreamsCorsAllowCredentials; + // Configured User Interface URL (default: http://localhost:4000) @Value("${dspace.ui.url:http://localhost:4000}") private String uiURL; @@ -91,6 +101,14 @@ public String[] getIiifAllowedOriginsConfig() { return this.iiifCorsAllowedOrigins; } + /** + * Returns the bitstream.cors.allowed-origins (for Bitstream access) defined in DSpace configuration. + * @return allowed origins + */ + public String[] getBitstreamAllowedOriginsConfig() { + return this.bitstreamCorsAllowedOrigins; + } + /** * Return whether to allow credentials (cookies) on CORS requests. This is used to set the * CORS "Access-Control-Allow-Credentials" header in Application class. @@ -108,4 +126,13 @@ public boolean getCorsAllowCredentials() { public boolean getIiifAllowCredentials() { return iiifCorsAllowCredentials; } + + /** + * Return whether to allow credentials (cookies) on IIIF requests. This is used to set the + * CORS "Access-Control-Allow-Credentials" header in Application class. Defaults to false. + * @return true or false + */ + public boolean getBitstreamsAllowCredentials() { + return bitstreamsCorsAllowCredentials; + } } diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 263520fb9e29..821469967967 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -243,6 +243,8 @@ db.schema = public # avoid trouble for such browsers (i.e. rest.cors.allowed-origins = ${dspace.ui.url}, https://samltest.id ) #rest.cors.allowed-origins = ${dspace.ui.url} +#bitstream.cors.allowed-origins = ${dspace.ui.url} + ################################################# # SPRING BOOT SETTINGS (Used by Server Webapp) # ################################################# diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 677396eefa94..9d2eb77be2cc 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -131,6 +131,11 @@ rest.report-url.item-query = static/reports/query.html # The following configuration setting will construct a SQL regular expression test appropriate to your database engine rest.regex-clause = text_value ~ ? +##### Customize the REST origins allowed to retrieve the bitstreams ##### +##### default is set to pattern * - use this configuration to restrict/modify this behavior +##### This configuration doens't support the wildcard +bitstream.cors.allowed-origins = + ##### Configure REST Report Filters ##### # A filter contains a set of tests that will be applied to an item to determine its inclusion in a particular report. From 4fafc2b7c17754ac094b202c55300caf3ed2b808 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Wed, 19 Jul 2023 12:58:36 +0200 Subject: [PATCH 382/394] [DURACOM-179] replaced 'null' value with exception actual value in sendErrorResponse method calls having 'null' --- .../exception/DSpaceApiExceptionControllerAdvice.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java index edefd7434e76..72133e3de530 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java @@ -137,7 +137,7 @@ protected void handleUnprocessableEntityException(HttpServletRequest request, Ht Exception ex) throws IOException { //422 is not defined in HttpServletResponse. Its meaning is "Unprocessable Entity". //Using the value from HttpStatus. - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "Unprocessable or invalid entity", HttpStatus.UNPROCESSABLE_ENTITY.value()); } @@ -145,7 +145,7 @@ protected void handleUnprocessableEntityException(HttpServletRequest request, Ht @ExceptionHandler( {InvalidSearchRequestException.class}) protected void handleInvalidSearchRequestException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "Invalid search request", HttpStatus.UNPROCESSABLE_ENTITY.value()); } @@ -184,7 +184,7 @@ protected void handleCustomUnprocessableEntityException(HttpServletRequest reque TranslatableException ex) throws IOException { Context context = ContextUtil.obtainContext(request); sendErrorResponse( - request, response, null, ex.getLocalizedMessage(context), HttpStatus.UNPROCESSABLE_ENTITY.value() + request, response, (Exception) ex, ex.getLocalizedMessage(context), HttpStatus.UNPROCESSABLE_ENTITY.value() ); } @@ -200,7 +200,7 @@ protected ResponseEntity handleCustomUnprocessableEditException(HttpServ protected void ParameterConversionException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "A required parameter is invalid", HttpStatus.BAD_REQUEST.value()); } @@ -209,7 +209,7 @@ protected void ParameterConversionException(HttpServletRequest request, HttpServ protected void MissingParameterException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { // we want the 400 status for missing parameters, see https://jira.lyrasis.org/browse/DS-4428 - sendErrorResponse(request, response, null, + sendErrorResponse(request, response, ex, "A required parameter is missing", HttpStatus.BAD_REQUEST.value()); } From 7a3fcfc25c19fd41571562bfceb044a342b80afd Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Wed, 19 Jul 2023 16:39:38 +0200 Subject: [PATCH 383/394] [DSC-1117] fix in CORS policies for bitstreams --- .../java/org/dspace/app/rest/Application.java | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java index 1c90a2e3e4f9..89c5c010b266 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java @@ -195,28 +195,19 @@ public void addCorsMappings(@NonNull CorsRegistry registry) { boolean iiifAllowCredentials = configuration.getIiifAllowCredentials(); boolean bitstreamAllowCredentials = configuration.getBitstreamsAllowCredentials(); - if (!ArrayUtils.isEmpty(bitstreamAllowedOrigins)) { - registry.addMapping("/api/core/bitstreams/**").allowedMethods(CorsConfiguration.ALL) - // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid - // for our Access-Control-Allow-Origin header - .allowCredentials(bitstreamAllowCredentials).allowedOrigins(bitstreamAllowedOrigins) - // Allow list of request preflight headers allowed to be sent to us from the client - .allowedHeaders("Accept", "Authorization", "Content-Type", "Origin", "X-On-Behalf-Of", - "X-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER", - "x-recaptcha-token", "Access-Control-Allow-Origin") - // Allow list of response headers allowed to be sent by us (the server) to the client - .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); - } else { - registry.addMapping("/api/core/bitstreams/**").allowedMethods(CorsConfiguration.ALL) - // Allow all orgin patterns by default - .allowedOriginPatterns(CorsConfiguration.ALL) - // Allow list of request preflight headers allowed to be sent to us from the client - .allowedHeaders("Accept", "Authorization", "Content-Type", "Origin", "X-On-Behalf-Of", - "X-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER", - "x-recaptcha-token") - // Allow list of response headers allowed to be sent by us (the server) to the client - .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); + if (ArrayUtils.isEmpty(bitstreamAllowedOrigins)) { + bitstreamAllowedOrigins = corsAllowedOrigins; } + registry.addMapping("/api/core/bitstreams/**").allowedMethods(CorsConfiguration.ALL) + // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid + // for our Access-Control-Allow-Origin header + .allowCredentials(bitstreamAllowCredentials).allowedOrigins(bitstreamAllowedOrigins) + // Allow list of request preflight headers allowed to be sent to us from the client + .allowedHeaders("Accept", "Authorization", "Content-Type", "Origin", "X-On-Behalf-Of", + "X-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER", + "x-recaptcha-token", "Access-Control-Allow-Origin") + // Allow list of response headers allowed to be sent by us (the server) to the client + .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); if (corsAllowedOrigins != null) { registry.addMapping("/api/**").allowedMethods(CorsConfiguration.ALL) // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid From 288d99b9676862d701c11ffa08ffa49a2781a533 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Wed, 19 Jul 2023 16:57:24 +0200 Subject: [PATCH 384/394] [DSC-1117] added check to avoid empty CORS policies to be set for bitstreams --- .../java/org/dspace/app/rest/Application.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java index 89c5c010b266..02fd8ad0166a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java @@ -198,16 +198,18 @@ public void addCorsMappings(@NonNull CorsRegistry registry) { if (ArrayUtils.isEmpty(bitstreamAllowedOrigins)) { bitstreamAllowedOrigins = corsAllowedOrigins; } - registry.addMapping("/api/core/bitstreams/**").allowedMethods(CorsConfiguration.ALL) - // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid - // for our Access-Control-Allow-Origin header - .allowCredentials(bitstreamAllowCredentials).allowedOrigins(bitstreamAllowedOrigins) - // Allow list of request preflight headers allowed to be sent to us from the client - .allowedHeaders("Accept", "Authorization", "Content-Type", "Origin", "X-On-Behalf-Of", - "X-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER", - "x-recaptcha-token", "Access-Control-Allow-Origin") - // Allow list of response headers allowed to be sent by us (the server) to the client - .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); + if (!ArrayUtils.isEmpty(bitstreamAllowedOrigins)) { + registry.addMapping("/api/core/bitstreams/**").allowedMethods(CorsConfiguration.ALL) + // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid + // for our Access-Control-Allow-Origin header + .allowCredentials(bitstreamAllowCredentials).allowedOrigins(bitstreamAllowedOrigins) + // Allow list of request preflight headers allowed to be sent to us from the client + .allowedHeaders("Accept", "Authorization", "Content-Type", "Origin", "X-On-Behalf-Of", + "X-Requested-With", "X-XSRF-TOKEN", "X-CORRELATION-ID", "X-REFERRER", + "x-recaptcha-token", "Access-Control-Allow-Origin") + // Allow list of response headers allowed to be sent by us (the server) to the client + .exposedHeaders("Authorization", "DSPACE-XSRF-TOKEN", "Location", "WWW-Authenticate"); + } if (corsAllowedOrigins != null) { registry.addMapping("/api/**").allowedMethods(CorsConfiguration.ALL) // Set Access-Control-Allow-Credentials to "true" and specify which origins are valid From cf58bab4f24a27b10bab5831783916de7d7256a1 Mon Sep 17 00:00:00 2001 From: Alexander K Date: Mon, 24 Jul 2023 12:31:09 +0200 Subject: [PATCH 385/394] [DSC-1088] Fix issn live import mapping field --- dspace/config/spring/api/crossref-integration.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/spring/api/crossref-integration.xml b/dspace/config/spring/api/crossref-integration.xml index e01b613833e4..35712a2983fb 100644 --- a/dspace/config/spring/api/crossref-integration.xml +++ b/dspace/config/spring/api/crossref-integration.xml @@ -107,7 +107,7 @@ - + From 84ccb7c9d3bd4799bc35ecfc6cd6da23a94a370b Mon Sep 17 00:00:00 2001 From: Alexander K Date: Mon, 24 Jul 2023 17:53:17 +0200 Subject: [PATCH 386/394] [DSC-1088] Fix tests --- .../app/rest/CrossRefImportMetadataSourceServiceIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java index 9a0d39225c3d..1b17215054ad 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrossRefImportMetadataSourceServiceIT.java @@ -150,7 +150,7 @@ private ArrayList getRecords() { MetadatumDTO ispartof = createMetadatumDTO("dc", "relation", "ispartof", "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"); MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.26693/jmbs01.02.184"); - MetadatumDTO issn = createMetadatumDTO("dc", "identifier", "issn", "2415-3060"); + MetadatumDTO issn = createMetadatumDTO("dc", "relation", "issn", "2415-3060"); MetadatumDTO volume = createMetadatumDTO("oaire", "citation", "volume", "1"); MetadatumDTO issue = createMetadatumDTO("oaire", "citation", "issue", "2"); @@ -176,7 +176,7 @@ private ArrayList getRecords() { MetadatumDTO ispartof2 = createMetadatumDTO("dc", "relation", "ispartof", "Ukraïnsʹkij žurnal medicini, bìologìï ta sportu"); MetadatumDTO doi2 = createMetadatumDTO("dc", "identifier", "doi", "10.26693/jmbs01.02.105"); - MetadatumDTO issn2 = createMetadatumDTO("dc", "identifier", "issn", "2415-3060"); + MetadatumDTO issn2 = createMetadatumDTO("dc", "relation", "issn", "2415-3060"); MetadatumDTO volume2 = createMetadatumDTO("oaire", "citation", "volume", "1"); MetadatumDTO issue2 = createMetadatumDTO("oaire", "citation", "issue", "2"); From 8637936d28f089a80f61bba1520345f3be6d421c Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Mon, 24 Jul 2023 20:34:02 +0200 Subject: [PATCH 387/394] [CST-10193][DSC-1150] handle lookup fallbacks on legacy oai identifier when checking authorization --- .../filter/DSpaceAuthorizationFilter.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/filter/DSpaceAuthorizationFilter.java b/dspace-oai/src/main/java/org/dspace/xoai/filter/DSpaceAuthorizationFilter.java index ebb19c84b5e2..baf8552a6029 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/filter/DSpaceAuthorizationFilter.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/filter/DSpaceAuthorizationFilter.java @@ -9,12 +9,17 @@ package org.dspace.xoai.filter; import java.sql.SQLException; +import java.util.Iterator; +import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; @@ -33,6 +38,9 @@ public class DSpaceAuthorizationFilter extends DSpaceFilter { private static final HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + private static final ItemService itemService + = ContentServiceFactory.getInstance().getItemService(); + @Override public boolean isShown(DSpaceItem item) { boolean pub = false; @@ -43,6 +51,11 @@ public boolean isShown(DSpaceItem item) { return false; } Item dspaceItem = (Item) handleService.resolveToObject(context, handle); + + if (dspaceItem == null) { + dspaceItem = fromLegacyIdentifier(item); + } + if (dspaceItem == null) { return false; } @@ -55,6 +68,25 @@ public boolean isShown(DSpaceItem item) { return pub; } + private Item fromLegacyIdentifier(DSpaceItem item) { + List legacyIdentifier = item.getMetadata("dspace.legacy.oai-identifier"); + if (legacyIdentifier.isEmpty()) { + return null; + } + try { + Iterator + iterator = itemService.findUnfilteredByMetadataField( + context, "dspace", "legacy", "oai-identifier", + legacyIdentifier.get(0)); + if (!iterator.hasNext()) { + return null; + } + return iterator.next(); + } catch (AuthorizeException | SQLException e) { + throw new RuntimeException(e); + } + } + @Override public SolrFilterResult buildSolrQuery() { return new SolrFilterResult("item.public:true"); From 1faf4ece7b92e1459a075f782801ee179b1988e8 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Tue, 25 Jul 2023 16:07:14 +0200 Subject: [PATCH 388/394] [CST-10514][DSC-1150] handle lookup fallbacks on legacy oai identifier when finding item to be linked to a topic --- .../service/impl/NBEventServiceImpl.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java index 4df11b054e67..e07fb16c72ca 100644 --- a/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/nbevent/service/impl/NBEventServiceImpl.java @@ -8,14 +8,17 @@ package org.dspace.app.nbevent.service.impl; import java.io.IOException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; +import java.util.Iterator; import java.util.List; import java.util.UUID; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.solr.client.solrj.SolrClient; @@ -33,6 +36,7 @@ import org.dspace.app.nbevent.NBTopic; import org.dspace.app.nbevent.dao.impl.NBEventsDaoImpl; import org.dspace.app.nbevent.service.NBEventService; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.NBEvent; import org.dspace.content.service.ItemService; @@ -316,18 +320,38 @@ private String getResourceUUID(Context context, String originalId) throws Except 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; + item = fromLegacyIdentifier(context, originalId); + return item == null ? null : item.getID().toString(); } } else { throw new RuntimeException("Malformed originalId " + originalId); } } + private Item fromLegacyIdentifier(Context context, String legacyIdentifier) { + if (StringUtils.isBlank(legacyIdentifier)) { + return null; + } + try { + Iterator + iterator = itemService.findUnfilteredByMetadataField( + context, "dspace", "legacy", "oai-identifier", + legacyIdentifier); + if (!iterator.hasNext()) { + return null; + } + return iterator.next(); + } catch (AuthorizeException | SQLException e) { + throw new RuntimeException(e); + } + } + // oai:www.openstarts.units.it:10077/21486 private String getHandleFromOriginalId(String originalId) { Integer startPosition = originalId.lastIndexOf(':'); From 2e7ff80c140373a62304a828b3d7aa16102b1af0 Mon Sep 17 00:00:00 2001 From: "aliaksei.bykau" Date: Fri, 4 Aug 2023 08:32:40 +0200 Subject: [PATCH 389/394] [DSC-1183] now when user is subscribed to Item updates, they do not receive only updates for metadata changes, but for all updates affecting related entities. --- .../subscriptions/ContentGenerator.java | 8 +++++- .../subscriptions/StatisticsGenerator.java | 2 +- ...scriptionEmailNotificationServiceImpl.java | 13 ++++++++-- .../dSpaceObjectsUpdates/ItemsUpdates.java | 25 +++++++++++-------- .../service/SubscriptionGenerator.java | 2 +- dspace/config/emails/subscriptions_content | 5 +++- .../api/subscriptions_email_configuration.xml | 4 +++ 7 files changed, 42 insertions(+), 17 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java index a913f2504a50..f60ac3c98edb 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/ContentGenerator.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import javax.annotation.Resource; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -31,6 +32,8 @@ import org.dspace.subscriptions.service.SubscriptionGenerator; import org.springframework.beans.factory.annotation.Autowired; + + /** * Implementation class of SubscriptionGenerator * which will handle the logic of sending the emails @@ -42,6 +45,7 @@ public class ContentGenerator implements SubscriptionGenerator private final Logger log = LogManager.getLogger(ContentGenerator.class); @SuppressWarnings("unchecked") + @Resource(name = "entityDissemination") private Map entityType2Disseminator = new HashMap(); @Autowired @@ -50,7 +54,8 @@ public class ContentGenerator implements SubscriptionGenerator @Override public void notifyForSubscriptions(Context context, EPerson ePerson, List indexableComm, - List indexableColl) { + List indexableColl, + List indexableItems) { try { if (Objects.nonNull(ePerson)) { Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson); @@ -58,6 +63,7 @@ public void notifyForSubscriptions(Context context, EPerson ePerson, email.addRecipient(ePerson.getEmail()); email.addArgument(generateBodyMail(context, indexableComm)); email.addArgument(generateBodyMail(context, indexableColl)); + email.addArgument(generateBodyMail(context, indexableItems)); email.send(); } } catch (Exception e) { diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java index ae5fd931da76..c1f9be368e27 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java @@ -46,7 +46,7 @@ public class StatisticsGenerator implements SubscriptionGenerator { @Override public void notifyForSubscriptions(Context c, EPerson ePerson, List crisMetricsList, - List crisMetricsList1) { + List crisMetricsList1, List crisMetricsList2) { // find statistics for all the subscribed objects try { // send the notification to the user diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java index 8fb01cd36e92..2a30b89af3f5 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/SubscriptionEmailNotificationServiceImpl.java @@ -9,6 +9,7 @@ import static org.dspace.core.Constants.COLLECTION; import static org.dspace.core.Constants.COMMUNITY; +import static org.dspace.core.Constants.ITEM; import static org.dspace.core.Constants.READ; import java.sql.SQLException; @@ -67,6 +68,7 @@ public SubscriptionEmailNotificationServiceImpl(Map public void perform(Context context, DSpaceRunnableHandler handler, String subscriptionType, String frequency) { List communityItems = new ArrayList<>(); List collectionsItems = new ArrayList<>(); + List items = new ArrayList<>(); try { List subscriptions = findAllSubscriptionsBySubscriptionTypeAndFrequency(context, subscriptionType, frequency); @@ -93,6 +95,11 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc .get(Collection.class.getSimpleName().toLowerCase()) .findUpdates(context, dSpaceObject, frequency); collectionsItems.addAll(getItems(context, ePerson, indexableCollectionItems)); + } else if (dSpaceObject.getType() == ITEM) { + List indexableCollectionItems = contentUpdates + .get(Item.class.getSimpleName().toLowerCase()) + .findUpdates(context, dSpaceObject, frequency); + items.addAll(getItems(context, ePerson, indexableCollectionItems)); } else { log.warn("found an invalid DSpace Object type ({}) among subscriptions to send", dSpaceObject.getType()); @@ -106,14 +113,16 @@ public void perform(Context context, DSpaceRunnableHandler handler, String subsc continue; } else { subscriptionType2generators.get(subscriptionType) - .notifyForSubscriptions(context, ePerson, communityItems, collectionsItems); + .notifyForSubscriptions(context, ePerson, communityItems, + collectionsItems, items); communityItems.clear(); collectionsItems.clear(); } } else { //in the end of the iteration subscriptionType2generators.get(subscriptionType) - .notifyForSubscriptions(context, ePerson, communityItems, collectionsItems); + .notifyForSubscriptions(context, ePerson, communityItems, + collectionsItems, items); } iterator++; } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java index bf0c1ab28e93..40fcd81dafa5 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/dSpaceObjectsUpdates/ItemsUpdates.java @@ -40,6 +40,7 @@ import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.subscriptions.ContentGenerator; import org.dspace.subscriptions.service.DSpaceObjectUpdates; +import org.springframework.beans.factory.annotation.Autowired; /** @@ -49,10 +50,20 @@ * @author Alba Aliu */ public class ItemsUpdates implements DSpaceObjectUpdates { - private final CollectionService collectionService; - private final CommunityService communityService; - private final ItemService itemService; + + @Autowired + private CollectionService collectionService; + + @Autowired + private CommunityService communityService; + + @Autowired + private ItemService itemService; + + @Autowired private DiscoveryConfigurationService searchConfigurationService; + + @Autowired private SearchService searchService; private final Logger log = org.apache.logging.log4j.LogManager.getLogger(ContentGenerator.class); @@ -189,12 +200,4 @@ private DiscoverQuery buildBaseQuery(DiscoveryConfiguration discoveryConfigurati return discoverQuery; } - public ItemsUpdates(CollectionService collectionService, CommunityService communityService, ItemService itemService, - DiscoveryConfigurationService searchConfigurationService, SearchService searchService) { - this.collectionService = collectionService; - this.communityService = communityService; - this.itemService = itemService; - this.searchConfigurationService = searchConfigurationService; - this.searchService = searchService; - } } diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java index 1790513b9b79..994ada75b61b 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/service/SubscriptionGenerator.java @@ -20,6 +20,6 @@ */ public interface SubscriptionGenerator { - public void notifyForSubscriptions(Context c, EPerson ePerson, List comm, List coll); + public void notifyForSubscriptions(Context c, EPerson ePerson, List comm, List coll, List items); } \ No newline at end of file diff --git a/dspace/config/emails/subscriptions_content b/dspace/config/emails/subscriptions_content index 2b8289af278c..fc186abbb0b9 100644 --- a/dspace/config/emails/subscriptions_content +++ b/dspace/config/emails/subscriptions_content @@ -2,7 +2,7 @@ ## ## Parameters: {0} Collections updates ## {1} Communities updates -#set($subject = 'DSpace: Your Content Subscriptions') +## {2} Items updates This email is sent from DSpace-CRIS based on the chosen subscription preferences. @@ -14,3 +14,6 @@ Collections ----------- List of changed items : ${params[1]} +Items +----- +List of changed items : ${params[2]} \ No newline at end of file diff --git a/dspace/config/spring/api/subscriptions_email_configuration.xml b/dspace/config/spring/api/subscriptions_email_configuration.xml index 488bede0f385..c946a29bec7e 100644 --- a/dspace/config/spring/api/subscriptions_email_configuration.xml +++ b/dspace/config/spring/api/subscriptions_email_configuration.xml @@ -29,6 +29,7 @@ + @@ -63,4 +64,7 @@ + + + From 52e08ad4270c3d55c6d4a35d7c9ef22fd05338f8 Mon Sep 17 00:00:00 2001 From: Stefano Maffei Date: Thu, 17 Aug 2023 11:11:29 +0200 Subject: [PATCH 390/394] [DSC-1117] changes in configuration for bitstream rest cors policies --- .../java/org/dspace/app/rest/utils/ApplicationConfig.java | 4 ++-- dspace/config/local.cfg.EXAMPLE | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java index 38feba0bc1d3..67983ba8f007 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java @@ -38,7 +38,7 @@ public class ApplicationConfig { // Allowed IIIF CORS origins ("Access-Control-Allow-Origin" header) // Can be overridden in DSpace configuration - @Value("${bitstream.cors.allowed-origins}") + @Value("${rest.cors.bitstream-allow-origins}") private String[] bitstreamCorsAllowedOrigins; // Whether to allow credentials (cookies) in CORS requests ("Access-Control-Allow-Credentials" header) @@ -53,7 +53,7 @@ public class ApplicationConfig { // Whether to allow credentials (cookies) in CORS requests ("Access-Control-Allow-Credentials" header) // Defaults to true. Can be overridden in DSpace configuration - @Value("${bitstream.cors.allow-credentials:true}") + @Value("${rest.cors.bitstream-allow-credentials:true}") private boolean bitstreamsCorsAllowCredentials; // Configured User Interface URL (default: http://localhost:4000) diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 821469967967..78f5bad26770 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -243,7 +243,7 @@ db.schema = public # avoid trouble for such browsers (i.e. rest.cors.allowed-origins = ${dspace.ui.url}, https://samltest.id ) #rest.cors.allowed-origins = ${dspace.ui.url} -#bitstream.cors.allowed-origins = ${dspace.ui.url} +#rest.cors.bitstream-allowed-origins = ${dspace.ui.url} ################################################# # SPRING BOOT SETTINGS (Used by Server Webapp) # From 0f85176e859f4495d4631b77e6ed6e299a5bdd64 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 21 Aug 2023 12:31:36 +0200 Subject: [PATCH 391/394] [DSC-1125] Update Datacite schema on version 4 --- .../identifier/doi/DataCiteConnector.java | 135 +++++++----------- ...rter-datacitePublicationLicense.properties | 8 ++ ...erter-datacitePublicationRights.properties | 4 + ...verter-datacitePublicationTypes.properties | 50 +++++++ .../template/patent-datacite-xml.template | 43 ++++++ .../template/product-datacite-xml.template | 42 ++++++ .../publication-datacite-xml.template | 43 ++++++ dspace/config/spring/api/crosswalks.xml | 51 +++++++ .../config/spring/api/identifier-service.xml | 13 +- 9 files changed, 304 insertions(+), 85 deletions(-) create mode 100644 dspace/config/crosswalks/mapConverter-datacitePublicationLicense.properties create mode 100644 dspace/config/crosswalks/mapConverter-datacitePublicationRights.properties create mode 100644 dspace/config/crosswalks/mapConverter-datacitePublicationTypes.properties create mode 100644 dspace/config/crosswalks/template/patent-datacite-xml.template create mode 100644 dspace/config/crosswalks/template/product-datacite-xml.template create mode 100644 dspace/config/crosswalks/template/publication-datacite-xml.template diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index 57136d6143bb..bd5791481e90 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -8,13 +8,14 @@ package org.dspace.identifier.doi; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URISyntaxException; import java.sql.SQLException; -import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; @@ -35,13 +36,14 @@ import org.apache.http.util.EntityUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; import org.dspace.content.crosswalk.CrosswalkException; -import org.dspace.content.crosswalk.DisseminationCrosswalk; -import org.dspace.content.crosswalk.ParameterizedDisseminationCrosswalk; +import org.dspace.content.crosswalk.StreamDisseminationCrosswalk; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.core.factory.CoreServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.identifier.DOI; import org.dspace.services.ConfigurationService; @@ -99,18 +101,6 @@ public class DataCiteConnector * dependency injection. */ protected String METADATA_PATH; - /** - * Name of crosswalk to convert metadata into DataCite Metadata Scheme. Set - * by spring dependency injection. - */ - protected String CROSSWALK_NAME; - /** - * DisseminationCrosswalk to map local metadata into DataCite metadata. - * The name of the crosswalk is set by spring dependency injection using - * {@link #setDisseminationCrosswalkName(String) setDisseminationCrosswalkName} which - * instantiates the crosswalk. - */ - protected ParameterizedDisseminationCrosswalk xwalk; protected ConfigurationService configurationService; @@ -119,8 +109,12 @@ public class DataCiteConnector @Autowired protected HandleService handleService; + @Autowired + private ItemService itemService; + + private Map disseminationCrosswalkByEntityType; + public DataCiteConnector() { - this.xwalk = null; this.USERNAME = null; this.PASSWORD = null; } @@ -189,34 +183,6 @@ public void setConfigurationService(ConfigurationService configurationService) { this.configurationService = configurationService; } - /** - * Set the name of the dissemination crosswalk used to convert the metadata - * into DataCite Metadata Schema. Used by spring dependency injection. - * - * @param CROSSWALK_NAME The name of the dissemination crosswalk to use. This - * crosswalk must be configured in dspace.cfg. - */ - @Autowired(required = true) - public void setDisseminationCrosswalkName(String CROSSWALK_NAME) { - this.CROSSWALK_NAME = CROSSWALK_NAME; - } - - protected void prepareXwalk() { - if (null != this.xwalk) { - return; - } - - this.xwalk = (ParameterizedDisseminationCrosswalk) CoreServiceFactory.getInstance().getPluginService() - .getNamedPlugin( - DisseminationCrosswalk.class, - this.CROSSWALK_NAME); - - if (this.xwalk == null) { - throw new RuntimeException("Can't find crosswalk '" - + CROSSWALK_NAME + "'!"); - } - } - protected String getUsername() { if (null == this.USERNAME) { this.USERNAME = this.configurationService.getProperty(CFG_USER); @@ -350,64 +316,43 @@ public void deleteDOI(Context context, String doi) @Override public void reserveDOI(Context context, DSpaceObject dso, String doi) throws DOIIdentifierException { - this.prepareXwalk(); DSpaceObjectService dSpaceObjectService = ContentServiceFactory.getInstance() .getDSpaceObjectService(dso); - if (!this.xwalk.canDisseminate(dso)) { - log.error("Crosswalk " + this.CROSSWALK_NAME - + " cannot disseminate DSO with type " + dso.getType() - + " and ID " + dso.getID() + ". Giving up reserving the DOI " - + doi + "."); - throw new DOIIdentifierException("Cannot disseminate " - + dSpaceObjectService.getTypeText(dso) + "/" + dso.getID() - + " using crosswalk " + this.CROSSWALK_NAME + ".", - DOIIdentifierException.CONVERSION_ERROR); - } + StreamDisseminationCrosswalk xwalk = getStreamDisseminationCrosswalkByDso(dso); - // Set the transform's parameters. - // XXX Should the actual list be configurable? - Map parameters = new HashMap<>(); - if (configurationService.hasProperty(CFG_PREFIX)) { - parameters.put("prefix", - configurationService.getProperty(CFG_PREFIX)); - } - if (configurationService.hasProperty(CFG_PUBLISHER)) { - parameters.put("publisher", - configurationService.getProperty(CFG_PUBLISHER)); - } - if (configurationService.hasProperty(CFG_DATAMANAGER)) { - parameters.put("datamanager", - configurationService.getProperty(CFG_DATAMANAGER)); - } - if (configurationService.hasProperty(CFG_HOSTINGINSTITUTION)) { - parameters.put("hostinginstitution", - configurationService.getProperty(CFG_HOSTINGINSTITUTION)); + if (xwalk == null) { + log.error("No crosswalk found for DSO with type " + dso.getType() + + " and ID " + dso.getID() + ". Giving up reserving the DOI " + + doi + "."); + throw new DOIIdentifierException("Cannot disseminate " + + dSpaceObjectService.getTypeText(dso) + "/" + dso.getID() + ".", + DOIIdentifierException.CONVERSION_ERROR); } Element root = null; try { - root = xwalk.disseminateElement(context, dso, parameters); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + xwalk.disseminate(context, dso, baos); + SAXBuilder builder = new SAXBuilder(); + Document document = builder.build(new ByteArrayInputStream(baos.toByteArray())); + root = document.getRootElement(); } catch (AuthorizeException ae) { log.error("Caught an AuthorizeException while disseminating DSO " + "with type " + dso.getType() + " and ID " + dso.getID() + ". Giving up to reserve DOI " + doi + ".", ae); throw new DOIIdentifierException("AuthorizeException occured while " - + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso - .getID() - + " using crosswalk " + this.CROSSWALK_NAME + ".", ae, + + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso + ".", ae, DOIIdentifierException.CONVERSION_ERROR); } catch (CrosswalkException ce) { log.error("Caught an CrosswalkException while reserving a DOI (" + doi + ") for DSO with type " + dso.getType() + " and ID " + dso.getID() + ". Won't reserve the doi.", ce); throw new DOIIdentifierException("CrosswalkException occured while " - + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso - .getID() - + " using crosswalk " + this.CROSSWALK_NAME + ".", ce, + + "converting " + dSpaceObjectService.getTypeText(dso) + "/" + dso + ".", ce, DOIIdentifierException.CONVERSION_ERROR); - } catch (IOException | SQLException ex) { + } catch (IOException | SQLException | JDOMException ex) { throw new RuntimeException(ex); } @@ -462,6 +407,21 @@ public void reserveDOI(Context context, DSpaceObject dso, String doi) } } + private StreamDisseminationCrosswalk getStreamDisseminationCrosswalkByDso(DSpaceObject dso) { + + if (dso.getType() != Constants.ITEM) { + return null; + } + + String entityType = itemService.getEntityType((Item) dso); + if (StringUtils.isBlank(entityType)) { + entityType = "Publication"; + } + + return disseminationCrosswalkByEntityType.get(entityType); + + } + @Override public void registerDOI(Context context, DSpaceObject dso, String doi) throws DOIIdentifierException { @@ -631,7 +591,7 @@ protected DataCiteResponse sendMetadataPostRequest(String doi, Element metadataR Format format = Format.getCompactFormat(); format.setEncoding("UTF-8"); XMLOutputter xout = new XMLOutputter(format); - return sendMetadataPostRequest(doi, xout.outputString(new Document(metadataRoot))); + return sendMetadataPostRequest(doi, xout.outputString(metadataRoot.getDocument())); } protected DataCiteResponse sendMetadataPostRequest(String doi, String metadata) @@ -848,6 +808,15 @@ protected Element addDOI(String doi, Element root) { return root.addContent(0, identifier); } + public Map getDisseminationCrosswalkByEntityType() { + return disseminationCrosswalkByEntityType; + } + + public void setDisseminationCrosswalkByEntityType( + Map disseminationCrosswalkByEntityType) { + this.disseminationCrosswalkByEntityType = disseminationCrosswalkByEntityType; + } + protected class DataCiteResponse { private final int statusCode; private final String content; diff --git a/dspace/config/crosswalks/mapConverter-datacitePublicationLicense.properties b/dspace/config/crosswalks/mapConverter-datacitePublicationLicense.properties new file mode 100644 index 000000000000..05bcf86c28da --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-datacitePublicationLicense.properties @@ -0,0 +1,8 @@ +CC\ BY = https://creativecommons.org/licenses/by/4.0/legalcode +CC\ BY\-SA = https://creativecommons.org/licenses/by-sa/4.0/legalcode +CC\ BY\-ND = https://creativecommons.org/licenses/by-nd/4.0/legalcode +CC\ BY\-NC = https://creativecommons.org/licenses/by-nc/4.0/legalcode +CC\ BY\-NC\-SA = https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode +CC\ BY\-NC\-ND = https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode +CC0 = https://creativecommons.org/share-your-work/public-domain/cc0/ +PDM = https://creativecommons.org/publicdomain/mark/1.0/ \ No newline at end of file diff --git a/dspace/config/crosswalks/mapConverter-datacitePublicationRights.properties b/dspace/config/crosswalks/mapConverter-datacitePublicationRights.properties new file mode 100644 index 000000000000..049024f7dc56 --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-datacitePublicationRights.properties @@ -0,0 +1,4 @@ +openaccess = http://purl.org/coar/access_right/c_abf2 +embargoed = http://purl.org/coar/access_right/c_f1cf +restricted = http://purl.org/coar/access_right/c_16ec +metadata\ only = http://purl.org/coar/access_right/c_14cb \ No newline at end of file diff --git a/dspace/config/crosswalks/mapConverter-datacitePublicationTypes.properties b/dspace/config/crosswalks/mapConverter-datacitePublicationTypes.properties new file mode 100644 index 000000000000..0ef150d6f8f8 --- /dev/null +++ b/dspace/config/crosswalks/mapConverter-datacitePublicationTypes.properties @@ -0,0 +1,50 @@ +Resource\ Types\:\:text = Text +Resource\ Types\:\:text\:\:annotation = Text +Resource\ Types\:\:text\:\:bibliography = Text +Resource\ Types\:\:text\:\:blog\ post = Text +Resource\ Types\:\:text\:\:book = Book +Resource\ Types\:\:text\:\:book\:\:book\ part = BookChapter +Resource\ Types\:\:text\:\:conference\ output = ConferencePaper +Resource\ Types\:\:text\:\:conference\ output\:\:conference paper not in proceedings = ConferencePaper +Resource\ Types\:\:text\:\:conference\ output\:\:conference poster not in proceedings = ConferencePaper +Resource\ Types\:\:text\:\:conference\ output\:\:conference presentation = ConferenceProceeding +Resource\ Types\:\:text\:\:conference\ output\:\:conference proceedings = ConferenceProceeding +Resource\ Types\:\:text\:\:journal = Journal +Resource\ Types\:\:text\:\:journal\:\:editorial = Journal +Resource\ Types\:\:text\:\:journal\:\:journal\ article = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:corrigendum = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:data\ paper = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:research\ article = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:review\ article = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:journal\ article\:\:software\ paper = JournalArticle +Resource\ Types\:\:text\:\:journal\:\:letter\ to\ the\ editor = Journal +Resource\ Types\:\:text\:\:lecture = Text +Resource\ Types\:\:text\:\:letter = Text +Resource\ Types\:\:text\:\:magazine = Text +Resource\ Types\:\:text\:\:manuscript = Text +Resource\ Types\:\:text\:\:musical\ notation = Sound +Resource\ Types\:\:text\:\:newspaper = Text +Resource\ Types\:\:text\:\:newspaper\:\:newspaper\ article = Text +Resource\ Types\:\:text\:\:other\ periodical = Text +Resource\ Types\:\:text\:\:preprint = Preprint +Resource\ Types\:\:text\:\:report = Report +Resource\ Types\:\:text\:\:report\:\:clinical\ study = Report +Resource\ Types\:\:text\:\:report\:\:data\ management\ plan = OutputManagementPlan +Resource\ Types\:\:text\:\:report\:\:memorandum = Report +Resource\ Types\:\:text\:\:report\:\:policy\ report = Report +Resource\ Types\:\:text\:\:report\:\:project\ deliverable = Report +Resource\ Types\:\:text\:\:report\:\:research\ protocol = Report +Resource\ Types\:\:text\:\:report\:\:research\ report = Report +Resource\ Types\:\:text\:\:report\:\:technical\ report = Report +Resource\ Types\:\:text\:\:research\ proposal = Text +Resource\ Types\:\:text\:\:review = PeerReview +Resource\ Types\:\:text\:\:review\:\:book\ review = PeerReview +Resource\ Types\:\:text\:\:review\:\:commentary = PeerReview +Resource\ Types\:\:text\:\:review\:\:peer\ review = PeerReview +Resource\ Types\:\:text\:\:technical\ documentation = Text +Resource\ Types\:\:text\:\:thesis = Dissertation +Resource\ Types\:\:text\:\:thesis\:\:bachelor\ thesis = Dissertation +Resource\ Types\:\:text\:\:thesis\:\:doctoral\ thesis = Dissertation +Resource\ Types\:\:text\:\:thesis\:\:master\ thesis = Dissertation +Resource\ Types\:\:text\:\:transcription = Text +Resource\ Types\:\:text\:\:working\ paper = Preprint diff --git a/dspace/config/crosswalks/template/patent-datacite-xml.template b/dspace/config/crosswalks/template/patent-datacite-xml.template new file mode 100644 index 000000000000..22b400e96926 --- /dev/null +++ b/dspace/config/crosswalks/template/patent-datacite-xml.template @@ -0,0 +1,43 @@ + + + @dc.identifier.doi@ + + @group.dc-contributor-author.start@ + + @dc.contributor.author@ + @relation.dc-contributor-author.start@ + @person.identifier.orcid@ + @relation.dc-contributor-author.end@ + @oairecerif.author.affiliation@ + + @group.dc-contributor-author.end@ + + + @dc.title@ + + @dc.publisher@ + @virtual.date.dc-date-issued.YYYY@ + + @dc.subject@ + + + @dc.date.issued@ + @datacite.available@ + + @dc.language.iso@ + + + @dc.identifier.uri@ + + @dc.description.version@ + + + @oaire.licenseCondition@ + + @datacite.rights@ + + + @dc.description.abstract@ + @dc.description@ + + \ No newline at end of file diff --git a/dspace/config/crosswalks/template/product-datacite-xml.template b/dspace/config/crosswalks/template/product-datacite-xml.template new file mode 100644 index 000000000000..50224aa35cca --- /dev/null +++ b/dspace/config/crosswalks/template/product-datacite-xml.template @@ -0,0 +1,42 @@ + + + @dc.identifier.doi@ + + @group.dc-contributor-author.start@ + + @dc.contributor.author@ + @relation.dc-contributor-author.start@ + @person.identifier.orcid@ + @relation.dc-contributor-author.end@ + @oairecerif.author.affiliation@ + + @group.dc-contributor-author.end@ + + + @dc.title@ + + @dc.publisher@ + @virtual.date.dc-date-issued.YYYY@ + + @dc.subject@ + + + @dc.date.issued@ + + @dc.language.iso@ + + + @dc.identifier.uri@ + + @dc.description.version@ + + + @oaire.licenseCondition@ + + @datacite.rights@ + + + @dc.description.abstract@ + @dc.description@ + + \ No newline at end of file diff --git a/dspace/config/crosswalks/template/publication-datacite-xml.template b/dspace/config/crosswalks/template/publication-datacite-xml.template new file mode 100644 index 000000000000..22b400e96926 --- /dev/null +++ b/dspace/config/crosswalks/template/publication-datacite-xml.template @@ -0,0 +1,43 @@ + + + @dc.identifier.doi@ + + @group.dc-contributor-author.start@ + + @dc.contributor.author@ + @relation.dc-contributor-author.start@ + @person.identifier.orcid@ + @relation.dc-contributor-author.end@ + @oairecerif.author.affiliation@ + + @group.dc-contributor-author.end@ + + + @dc.title@ + + @dc.publisher@ + @virtual.date.dc-date-issued.YYYY@ + + @dc.subject@ + + + @dc.date.issued@ + @datacite.available@ + + @dc.language.iso@ + + + @dc.identifier.uri@ + + @dc.description.version@ + + + @oaire.licenseCondition@ + + @datacite.rights@ + + + @dc.description.abstract@ + @dc.description@ + + \ No newline at end of file diff --git a/dspace/config/spring/api/crosswalks.xml b/dspace/config/spring/api/crosswalks.xml index 8af3936f4806..504645bd83b6 100644 --- a/dspace/config/spring/api/crosswalks.xml +++ b/dspace/config/spring/api/crosswalks.xml @@ -19,6 +19,7 @@ + @@ -59,8 +60,10 @@ + + @@ -283,6 +286,15 @@ + + + + + + + + + @@ -332,6 +344,24 @@ + + + + + + + + + + + + + + + + + + @@ -568,6 +598,9 @@ + + + @@ -644,6 +677,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/spring/api/identifier-service.xml b/dspace/config/spring/api/identifier-service.xml index dbcd49df0e92..39754edce46b 100644 --- a/dspace/config/spring/api/identifier-service.xml +++ b/dspace/config/spring/api/identifier-service.xml @@ -1,8 +1,11 @@ + http://www.springframework.org/schema/beans/spring-beans-2.5.xsd + http://www.springframework.org/schema/util + http://www.springframework.org/schema/util/spring-util.xsd"> From 5766d8cd4cfac7452463b3ae3e49391ad9b18f33 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 21 Aug 2023 12:45:39 +0200 Subject: [PATCH 392/394] [DSC-1125] Fixed tests --- .../dspaceFolder/config/spring/api/identifier-service.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml index 206b801d0842..12a3b4e9565d 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/identifier-service.xml @@ -41,6 +41,12 @@ - + + + + + + + From b14d87f62e4ef2a0e59d0b1af0cd522bbb86bc7e Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 23 Aug 2023 15:08:26 +0200 Subject: [PATCH 393/394] [DSC-1125] Upgrade namespace --- .../main/java/org/dspace/identifier/doi/DataCiteConnector.java | 2 +- dspace/config/dspace.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index bd5791481e90..43882918cd4a 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -802,7 +802,7 @@ protected Element addDOI(String doi, Element root) { } Element identifier = new Element("identifier", configurationService.getProperty(CFG_NAMESPACE, - "http://datacite.org/schema/kernel-3")); + "http://datacite.org/schema/kernel-4")); identifier.setAttribute("identifierType", "DOI"); identifier.addContent(doi.substring(DOI.SCHEME.length())); return root.addContent(0, identifier); diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 2fa3578cd90e..7db34a16b978 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -621,7 +621,7 @@ crosswalk.dissemination.DataCite.preferList = false crosswalk.dissemination.DataCite.publisher = My University #crosswalk.dissemination.DataCite.dataManager = # defaults to publisher #crosswalk.dissemination.DataCite.hostingInstitution = # defaults to publisher -crosswalk.dissemination.DataCite.namespace = http://datacite.org/schema/kernel-3 +crosswalk.dissemination.DataCite.namespace = http://datacite.org/schema/kernel-4 # Crosswalk Plugin Configuration: # The purpose of Crosswalks is to translate an external metadata format to/from From 8264b30b9a0959c2d1343936f9fabf44ceba0350 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 31 Aug 2023 11:00:59 +0200 Subject: [PATCH 394/394] [DSC-1214] Fixed virtual field place after item update --- .../impl/RelatedEntityItemEnhancer.java | 33 +++++++-- .../consumer/ItemEnhancerConsumerIT.java | 70 +++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java b/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java index a5d95582e41d..a6c97cc84e65 100644 --- a/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java +++ b/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java @@ -78,7 +78,7 @@ private void cleanObsoleteVirtualFields(Context context, Item item) throws SQLEx } private void updateVirtualFieldsPlaces(Context context, Item item) { - List virtualSourceFields = getMetadataValues(item, getVirtualSourceMetadataField()); + List virtualSourceFields = getVirtualSourceFields(item); for (MetadataValue virtualSourceField : virtualSourceFields) { metadataWithPlaceToUpdate(item, virtualSourceField) .ifPresent(updatePlaces(item, virtualSourceField)); @@ -113,9 +113,9 @@ private List getObsoleteVirtualFields(Item item) { List obsoleteVirtualFields = new ArrayList<>(); - List virtualSourceFields = getMetadataValues(item, getVirtualSourceMetadataField()); + List virtualSourceFields = getVirtualSourceFields(item); for (MetadataValue virtualSourceField : virtualSourceFields) { - if (isRelatedSourceNoMorePresent(item, virtualSourceField)) { + if (!isPlaceholder(virtualSourceField) && isRelatedSourceNoMorePresent(item, virtualSourceField)) { obsoleteVirtualFields.add(virtualSourceField); getRelatedVirtualField(item, virtualSourceField).ifPresent(obsoleteVirtualFields::add); } @@ -131,7 +131,7 @@ private boolean isRelatedSourceNoMorePresent(Item item, MetadataValue virtualSou } private Optional getRelatedVirtualField(Item item, MetadataValue virtualSourceField) { - return getMetadataValues(item, getVirtualMetadataField()).stream() + return getVirtualFields(item).stream() .filter(metadataValue -> metadataValue.getPlace() == virtualSourceField.getPlace()) .findFirst(); } @@ -141,6 +141,7 @@ private void performEnhancement(Context context, Item item) throws SQLException if (noEnhanceableMetadata(context, item)) { return; } + for (MetadataValue metadataValue : getEnhanceableMetadataValue(item)) { if (wasValueAlreadyUsedForEnhancement(item, metadataValue)) { @@ -191,9 +192,19 @@ private List getEnhanceableMetadataValue(Item item) { } private boolean wasValueAlreadyUsedForEnhancement(Item item, MetadataValue metadataValue) { - return getMetadataValues(item, getVirtualSourceMetadataField()).stream() + + if (isPlaceholderAtPlace(getVirtualFields(item), metadataValue.getPlace())) { + return true; + } + + return getVirtualSourceFields(item).stream() .anyMatch(virtualSourceField -> virtualSourceField.getPlace() == metadataValue.getPlace() && hasAuthorityEqualsTo(metadataValue, virtualSourceField.getValue())); + + } + + private boolean isPlaceholderAtPlace(List metadataValues, int place) { + return place < metadataValues.size() ? isPlaceholder(metadataValues.get(place)) : false; } private boolean hasAuthorityEqualsTo(MetadataValue metadataValue, String authority) { @@ -209,10 +220,22 @@ private Item findRelatedEntityItem(Context context, MetadataValue metadataValue) } } + private boolean isPlaceholder(MetadataValue metadataValue) { + return PLACEHOLDER_PARENT_METADATA_VALUE.equals(metadataValue.getValue()); + } + private List getMetadataValues(Item item, String metadataField) { return itemService.getMetadataByMetadataString(item, metadataField); } + private List getVirtualSourceFields(Item item) { + return getMetadataValues(item, getVirtualSourceMetadataField()); + } + + private List getVirtualFields(Item item) { + return getMetadataValues(item, getVirtualMetadataField()); + } + private void addVirtualField(Context context, Item item, String value) throws SQLException { itemService.addMetadata(context, item, VIRTUAL_METADATA_SCHEMA, VIRTUAL_METADATA_ELEMENT, getVirtualQualifier(), null, value); diff --git a/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java b/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java index 5e95c28f65b7..eee35a81d045 100644 --- a/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java +++ b/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java @@ -10,6 +10,7 @@ import static org.dspace.app.matcher.MetadataValueMatcher.with; import static org.dspace.core.CrisConstants.PLACEHOLDER_PARENT_METADATA_VALUE; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -290,6 +291,75 @@ public void testWithWorkspaceItem() throws Exception { } + @Test + @SuppressWarnings("unchecked") + public void testEnhancementAfterItemUpdate() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item person = ItemBuilder.createItem(context, collection) + .withTitle("Walter White") + .withOrcidIdentifier("0000-0000-1111-2222") + .build(); + + String personId = person.getID().toString(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Test publication") + .withEntityType("Publication") + .withAuthor("Jesse Pinkman") + .withAuthor("Saul Goodman") + .withAuthor("Walter White", person.getID().toString()) + .withAuthor("Gus Fring") + .build(); + + context.restoreAuthSystemState(); + publication = commitAndReload(publication); + + assertThat(getMetadataValues(publication, "dc.contributor.author"), contains( + with("dc.contributor.author", "Jesse Pinkman"), + with("dc.contributor.author", "Saul Goodman", 1), + with("dc.contributor.author", "Walter White", personId, 2, 600), + with("dc.contributor.author", "Gus Fring", 3))); + + assertThat(getMetadataValues(publication, "cris.virtual.author-orcid"), contains( + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 1), + with("cris.virtual.author-orcid", "0000-0000-1111-2222", 2), + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 3))); + + assertThat(getMetadataValues(publication, "cris.virtualsource.author-orcid"), contains( + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 1), + with("cris.virtualsource.author-orcid", personId, 2), + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 3))); + + context.turnOffAuthorisationSystem(); + itemService.addMetadata(context, publication, "dc", "title", "alternative", null, "Other name"); + itemService.update(context, publication); + context.restoreAuthSystemState(); + publication = commitAndReload(publication); + + assertThat(getMetadataValues(publication, "dc.contributor.author"), contains( + with("dc.contributor.author", "Jesse Pinkman"), + with("dc.contributor.author", "Saul Goodman", 1), + with("dc.contributor.author", "Walter White", personId, 2, 600), + with("dc.contributor.author", "Gus Fring", 3))); + + assertThat(getMetadataValues(publication, "cris.virtual.author-orcid"), contains( + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 1), + with("cris.virtual.author-orcid", "0000-0000-1111-2222", 2), + with("cris.virtual.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 3))); + + assertThat(getMetadataValues(publication, "cris.virtualsource.author-orcid"), contains( + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 1), + with("cris.virtualsource.author-orcid", personId, 2), + with("cris.virtualsource.author-orcid", PLACEHOLDER_PARENT_METADATA_VALUE, 3))); + + } + private MetadataValue getFirstMetadataValue(Item item, String metadataField) { return getMetadataValues(item, metadataField).get(0); }